muduo库源码分析: One Loop Per Thread

news2025/4/15 3:39:10

One Loop Per Thread的含义就是,一个EventLoop和一个线程唯一绑定,和这个EventLoop有关的,被这个EventLoop管辖的一切操作都必须在这个EventLoop绑定线程中执行

1.在MainEventLoop中,负责新连接建立的操作都要在MainEventLoop线程中运行。

2.已建立的连接分发到某个SubEventLoop上,这个已建立连接的任何操作,比如接收数据发送数据,连接断开等事件处理都必须在这个SubEventLoop线程上运行,还不准跑到别的SubEventLoop线程上运行。

一.主要成员

二.wakeupFd_

调用函数eventfd()会创建一个eventfd对象,或者也可以理解打开一个eventfd类型的文件,类似普通文件的open操作。

eventfd的在内核空间维护一个无符号64位整型计数器, 初始化为initval的值。

#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);

flags是以下三个标志位OR结果

  • EFD_CLOEXEC(2.6.27~) : eventfd()返回一个文件描述符,如果该进程被fork的时候,这个文件描述符也会被复制过去,这个时候就会有多个描述符指向同一个eventfd对象,如果设置了这个标志,则子进程在执行exec的时候,会自动清除掉父进程的这个文件描述符
  • EFD_NONBLOCK(2.6.27~):文件描述符会被设置为O_NONBLOCK,如果没有设置这个标志位,read操作的时候将会阻塞直到计数器中有值,如果设置了这个这个标志位,计数器没有值得时候也会立刻返回-1
  • EFD_SEMAPHORE(2.6.30~): 这个标志位会影响read操作。

三.EventLoop对象和一个线程唯一绑定

/***** EventLoop.cc *****/
__thread EventLoop *t_loopInThisThread = nullptr;

EventLoop::EventLoop() : 
    wakeupFd_(createEventfd()), //生成一个eventfd,每个EventLoop对象,都会有自己的eventfd
	wakeupChannel_(new Channel(this, wakeupFd_))
{
    LOG_DEBUG("EventLoop created %p in thread %d \n", this, threadId_);
    if(t_loopInThisThread) //如果当前线程已经绑定了某个EventLoop对象了,那么该线程就无法创建新的EventLoop对象了
        LOG_FATAL("Another EventLoop %p exits in this thread %d \n", t_loopInThisThread, threadId_);
    else
        t_loopInThisThread = this;
    wakeupChannel_->setReadCallback(std::bind(&EventLoop::handleRead, this));
    wakeupChannel_->enableReading(); 
}

介绍一下这个__thread,这个__thread是一个关键字,被这个关键字修饰的全局变量 t_loopInThisThread会具备一个属性,每个线程私有一份

在EventLoop对象的构造函数中,如果当前线程没有绑定EventLoop对象,那么t_loopInThisThread为nullptr,然后就让该指针变量指向EventLoop对象的地址。如果t_loopInThisThread不为nullptr,说明当前线程已经绑定了一个EventLoop对象了,这时候EventLoop对象构造失败!

四. EventLoop的线程只运行该Loop的操作

我们来描绘一个情景,我们知道每个EventLoop线程主要就是在执行其EventLoop对象的loop函数(该函数就是一个while循环,循环的获取事件监听器的结果以及调用每一个发生事件的Channel的事件处理函数)。此时SubEventLoop上注册的Tcp连接都没有任何动静,整个SubEventLoop线程就阻塞在epoll_wait()上。

EventLoop* ioLoop = threadPool_->getNextLoop();

ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));

此时MainEventLoop接受了一个新连接请求,并把这个新连接封装成一个TcpConnection对象,并且希望在SubEventLoop线程中执行TcpConnection::connectEstablished()函数,因为该函数的目的是将TcpConnection注册到SubEventLoop的事件监听器上,并且调用用户自定义的连接建立后的处理函数。当该TcpConnection对象注册到SubEventLoop之后,这个TcpConnection对象的任何操作(包括调用用户自定义的连接建立后的处理函数。)都必须要在这个SubEventLoop线程中运行,所以TcpConnection::connectEstablished()函数必须要在SubEventLoop线程中运行。

那么我们怎么在MainEventLoop线程中通知SubEventLoop线程起来执行TcpConnection::connectEstablished()函数呢?这里就要好好研究一下EventLoop::runInLoop()函数了。

void EventLoop::runInLoop(Functor cb)
{//该函数保证了cb这个函数对象一定是在其EventLoop线程中被调用。
    if(isInLoopThread())//如果当前调用runInLoop的线程正好是EventLoop的运行线程,则直接执行此函数
        cb();
    else//否则调用 queueInLoop 函数
        queueInLoop(cb);
}
void EventLoop::queueInLoop(Functor cb)
{
    {
        unique_lock<mutex> lock(mutex_);
        pendingFunctors_.emplace_back(cb);
    }
    if(!isInLoopThread() || callingPendingFunctors_)
        wakeup(); 
}

void EventLoop::wakeup()
{
    uint64_t one = 1;
    ssize_t n = write(wakeupFd_, &one, sizeof(one));
    if(n != sizeof(n))
        LOG_ERROR("EventLoop::wakeup() writes %lu bytes instead of 8 \n", n);
}

EventLoop* ioLoop = threadPool_->getNextLoop();

ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));

此时的Loop为线程池EventLoopThreadPool中取出的一个从Reactor

ioLoop调用runInLoop,将cb放入queue(pendingFunctors)中,调用wakeup唤醒ioLoop线程

void EventLoop::loop()
{ //EventLoop 所属线程执行
    looping_ = true;
    quit_ = false;
    LOG_INFO("EventLoop %p start looping \n", this);    
    while(!quit_)
    {
        activeChannels_.clear();
        pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);//此时activeChannels已经填好了事件发生的channel
        for(Channel *channel : activeChannels_)
            channel->HandlerEvent(pollReturnTime_);
        doPendingFunctors(); //执行当前EventLoop事件循环需要处理的回调操作。
    }
}
void EventLoop::doPendingFunctors()
{
   std::vector<Functor> functors;
   callingPendingFunctors_ = true;
   {
       unique_lock<mutex> lock(mutex_);
       functors.swap(pendingFunctors_); //这里的swap其实只是交换的vector对象指向的内存空间的指针而已。
   }
   for(const Functor &functor:functors)
   {
       functor();
   }
   callingPendingFunctors_ = false;
}

从Reactor的loop过程中,处理activeChannels_时,会包含wakeup_channel,此时可以接管TcpConnection,(已经建立且就绪的事件,就调用相应的回调函数即可)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2333982.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MCP结合高德地图完成配置

文章目录 1.MCP到底是什么2.cursor配置2.1配置之后的效果2.2如何进行正确的配置2.3高德地图获取key2.4选择匹配的模型 1.MCP到底是什么 作为学生&#xff0c;我们应该如何认识MCP&#xff1f;最近看到了好多跟MCP相关的文章&#xff0c;我觉得我们不应该盲目的追求热点的技术&…

重读《人件》Peopleware -(5)Ⅰ管理人力资源Ⅳ-质量—若时间允许

20世纪的心理学理论认为&#xff0c;人类的性格主要由少数几个基本本能所主导&#xff1a;生存、自尊、繁衍、领地等。这些本能直接嵌入大脑的“固件”中。我们可以在没有强烈情感的情况下理智地考虑这些本能&#xff08;就像你现在正在做的那样&#xff09;&#xff0c;但当我…

文献总结:AAAI2025-UniV2X-End-to-end autonomous driving through V2X cooperation

UniV2X 一、文章基本信息二、文章背景三、UniV2X框架1. 车路协同自动驾驶问题定义2. 稀疏-密集混合形态数据3. 交叉视图数据融合&#xff08;智能体融合&#xff09;4. 交叉视图数据融合&#xff08;车道融合&#xff09;5. 交叉视图数据融合&#xff08;占用融合&#xff09;6…

制造一只电子喵 (qwen2.5:0.5b 微调 LoRA 使用 llama-factory)

AI (神经网络模型) 可以认为是计算机的一种新的 “编程” 方式. 为了充分利用计算机, 只学习传统的编程 (编程语言/代码) 是不够的, 我们还要掌握 AI. 本文以 qwen2.5 和 llama-factory 举栗, 介绍语言模型 (LLM) 的微调 (LoRA SFT). 为了方便上手, 此处选择使用小模型 (qwen2…

Redis核心功能实现

前言 学习是个输入的过程&#xff0c;在进行输入之后再进行一些输出&#xff0c;比如写写文章&#xff0c;笔记&#xff0c;或者做一些技术串讲&#xff0c;虽然需要花费不少时间&#xff0c;但是好处很多&#xff0c;首先是能通过输出给自己的输入带来一些动力&#xff0c;然…

【连载3】基础智能体的进展与挑战综述

基础智能体的进展与挑战综述 从类脑智能到具备可进化性、协作性和安全性的系统 【翻译团队】刘军(liujunbupt.edu.cn) 钱雨欣玥 冯梓哲 李正博 李冠谕 朱宇晗 张霄天 孙大壮 黄若溪 2. 认知 人类认知是一种复杂的信息处理系统&#xff0c;它通过多个专门的神经回路协调运行…

MacOs java环境配置+maven环境配置踩坑实录

oracl官网下载jdk 1.8的安装包 注意可能需要注册&#xff01;&#xff01;&#xff01; 下载链接&#xff1a;下载地址点击 注意晚上就不要下载了 报错400 &#xff01;&#xff01;&#xff01; 1.点击安装嘛 2.配置环境变量 export JAVA_HOME/Library/Java/Java…

【Git】--- 企业级开发流程

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; Git 本篇博客我们讲解Git在企业开发中的整体流程&#xff0c;理解Git在实际企业开发中的高效设计。 &#x1f3e0; 企业级开发流程 一个软件从零开始到最…

蓝桥杯嵌入式历年省赛客观题

一.第十五届客观题 第十四届省赛 十三届 十二届

解决2080Ti使用节点ComfyUI-PuLID-Flux-Enhanced中遇到的问题

使用蓝大的工作流《一键同时换头、换脸、发型、发色之双pulid技巧》 刚开始遇到的是不支持bf16的错误 根据《bf16 is only supported on A100 GPUs #33》中提到&#xff0c;修改pulidflux.py中的dtype 为 dtype torch.float16 后&#xff0c;出现新的错误&#xff0c;这个…

LabVIEW驱动开发的解决思路

在科研项目中&#xff0c;常面临将其他语言开发的定制采集设备驱动转换为 LabVIEW 适用形式的难题。特别是当原驱动支持匮乏、开发人员技术支持不足时&#xff0c;如何抉择解决路径成为关键。以下提供具体解决思路&#xff0c;助力高效解决问题。 ​ 一、评估现有驱动死磕的可…

七、Qt框架编写的多线程应用程序

一、大纲 学习内容&#xff1a;使用两个线程&#xff0c;分别点击两个按钮&#xff0c;触发两个不同的效果 所需控件&#xff1a;两个button、三个label 涉及知识点&#xff1a;多线程、Qt的connect机制、定时器、互斥锁 需求&#xff1a; 1&#xff0c;多线程定时计数&#x…

MATLAB求和∑怎么用?

MATLAB求和∑怎么用&#xff1f; 一&#xff1a;题目&#xff1a;求下列方程的和 二、代码如下 1.syms函数 &#xff08;方法一) 代码如下&#xff08;示例&#xff09;&#xff1a; 1. syms x 2. symsum((x.^22*x).^3,1,100) 3. 2.直接用循环 (方法二) 代码如下&am…

项目二 使用miniedit创建拓扑

一、项目需求分析&#xff1a; 1. 在ubuntu的桌面环境中运行Mininet的图形化界面2. Mininet图形化界面中搭建拓扑并设置相关的设备和链路属性3. Floodlight中查看拓扑4. 完成Mininet的测试 二、项目实施步骤 1. 运行Mininet图形化界面 在“~/mininet/examples”目录下有一m…

Docker 镜像 的常用命令介绍

拉取镜像 $ docker pull imageName[:tag][:tag] tag 不写时&#xff0c;拉取的 是 latest 的镜像查看镜像 查看所有本地镜像 docker images or docker images -a查看完整的镜像的数字签名 docker images --digests查看完整的镜像ID docker images --no-trunc只查看所有的…

0x02.Redis 集群的实现原理是什么?

回答重点 Redis 集群&#xff08;Redis cluster&#xff09;是通过多个 Redis 实例组成的&#xff0c;每个主节点实例负责存储部分的数据&#xff0c;并且可以有一个或多个从节点作为备份。 具体是采用哈希槽&#xff08;Hash Slot&#xff09;机制来分配数据&#xff0c;将整…

浏览器多开

使用浏览器的用户功能&#xff0c;创建多个用户即可完成浏览器多开的需求&#xff0c;插件等相对独立 需要命名 然后就可以通过多个用户切换来实现多开了&#xff0c;不同任务选择不同用户

Redis常用数据结构和应用场景

一、前言 Redis提供了多种数据结构&#xff0c;每种结构对应不同的应用场景。本文对部分常用的核心数据结构和典型使用场景作出介绍。 二、String&#xff08;字符串&#xff09; 特点&#xff1a;二进制安全&#xff0c;可存储文本、数字、序列化对象等。场景&#xff1a; 缓…

【转载翻译】使用Open3D和Python进行点云处理

转自个人博客&#xff1a;【转载翻译】使用Open3D和Python进行点云处理 转载自&#xff1a;Point Cloud Processing with Open3D and Python 本文由 Carlos Melo 发布于2024年2月12日 本文很适合初学者对三维处理、点云处理以及Open3D库进行初步了解 另外&#xff0c;本文是基于…

用户登录不上linux服务器

一般出现这种问题&#xff0c;重新用root用户修改lsy用户的密码即可登录&#xff0c;但是当修改了还是登录不了的时候&#xff0c;去修改一个文件用root才能修改&#xff0c; 然后在最后添加上改用户的名字&#xff0c;例如 原本是只有user的&#xff0c;现在我加上了lsy了&a…