事件循环线程池的理解

  • 前置知识
  • reactor模型
    • thread::start()方法的理解
  • 创建线程池
  • 子线程被唤醒的几种情况
    • 子线程被主线程唤醒
      • 新连接到来
      • 有消息需要发送时(多reactor情况时)
    • 关闭连接时
    • 子线程被唤醒执行任务

在 上一篇中,我们讨论了关于简单的线程池的实现,此次我们就基于简单的线程池实现,深入剖析muduo网络库的事件循环线程池的代码:

前置知识

reactor模型

在这里插入图片描述

thread::start()方法的理解

#include "Thread.h"
#include "CurrentThread.h"#include <semaphore.h>std::atomic_int Thread::numCreated_(0);Thread::Thread(ThreadFunc func, const std::string &name): started_(false), joined_(false), tid_(0), func_(std::move(func)), name_(name)
{setDefaultName();
}Thread::~Thread()
{if (started_ && !joined_){   // thread类提供了设置分离线程的方法 线程运行后后台自动销毁(非阻塞)thread_->detach();                                                  }
}void Thread::start()                                                        // 一个Thread对象 记录的就是一个新线程的详细信息
{started_ = true;//定义一个信号量sem_t sem;//设置信号量sem,是用于线程间,初值为0sem_init(&sem, false, 0);                                               // false指的是 不设置进程间共享// 开启线程thread_ = std::shared_ptr<std::thread>(new std::thread([&]() {tid_ = CurrentThread::tid();                                        // 获取线程的tid值sem_post(&sem);//sem值+1func_();                                                            // 开启一个新线程 专门执行该线程函数}));// 这里必须等待获取上面新创建的线程的tid值sem_wait(&sem);
}void Thread::join()
{joined_ = true;thread_->join();//阻塞等待当前线程执行完毕
}void Thread::setDefaultName()
{int num = ++numCreated_;if (name_.empty()){char buf[32] = {0};snprintf(buf, sizeof buf, "Thread%d", num);name_ = buf;}
}

这个类是对std::thread进行了一个封装,主要关注Thread::start方法,核心实现是定义了一个信号量sem,以确保子线程能够成功被创建,具体的创建流程如下图。
在这里插入图片描述

创建线程池

在这里插入图片描述

  1. TcpServer启动线程池
  2. 在线程池中根据传入的线程数量创建事件循环线程EventLoopThread,并且将创建的事件循环线程加到线程池threads中去,并且调用EventLoopThread->loop函数,随后绑定返回的已经创建好事件循环EventLoop
  3. 创建事件循环EventLoopThread的时候绑定函数threadFunc
  4. 调用thread_.start()函数,thread_创建了一个子线程,该子线程执行绑定的threadFunc函数。在threadFunc函数中,创建事件循环EventLoop,创建成功了通知主线程,并在开始事件循环EventLoop.loop();与此同时,主线程在循环等待子线程成功创建EventLoop,并接收创建好的EventLoop对象,并返回。

上述过程可以总结为:主线程随着函数调用开辟子线程,子线程成功创建事件循环,并在在子线程中事件事件循环,实现了one loop per thread,主线程返回到创建事件循环线程池中,继续执行其他任务。如下图所示。
在这里插入图片描述
关于主线程的生命周期的理解

EventLoop *EventLoopThread::startLoop()
{thread_.start(); // 启用底层线程Thread类对象thread_中通过start()创建的线程EventLoop *loop = nullptr;{std::unique_lock<std::mutex> lock(mutex_);cond_.wait(lock, [this](){return loop_ != nullptr;});loop = loop_;}return loop;
}

这里是存在开辟了一个新的线程,可以称为子线程,子线程中是执行eventloop.loop()事件循环的,在程序的运行过程中不会被回收;但是这个主线程的,随着return loop;的返回,主线程是不是会被释放呢?其实不是的,这里的主线程可以看成是负责整个项目的实现的核心线程,当主线程执行startLoop()函数后创建了子线程并返回,主线程就继续执行其他的函数,并不是说返回了就被销毁了。
比如说主线程执行完startLoop()的返回顺序是:EventLoopThread::startLoop()—>>> EventLoopThreadPool::start—>>>然后执行TcpServer::start()中的loop_->runInLoop(std::bind(&Acceptor::listen, acceptor_.get())),跟调用顺序是相反的。见上图

感悟:关于这一点理解起来还是花了挺久的,这个项目前前后后也是花了挺多时间看了,但是总是存在这里那里的小问题,不影响理解整体项目,但是这些小点是真的才能感受到项目的高明;也是花时间整理这些小知识点,才可以有更深的感悟。

子线程被唤醒的几种情况

首先,我们需要强调一下,这里存在mainloop(一个)和subloop(多个),mainloop所在的线程为主线程,subloop所在的线程为子线程(可以理解为,subloop1对应为子线程1,subloop2对应为子线程2等)

子线程被主线程唤醒

新连接到来

当一切都初始化好后,MainLoop的Acceptor开始监听,当有新的连接到来时,触发新连接到来的回调函数,在回调函数中处理新连接的相关操作。
在这里插入图片描述

  1. 调用getNextLoop()函数
  2. 执行轮询算法返回一个事件循环EventLoop
  3. 对得到的事件循环loop绑定TCP连接,并且设置回调函数(通过TcpConnection设置,但最终是设置到EventLoop中的Channel回调函数中),见下图(图片来源万字长文梳理Muduo库核心代码及优秀编程细节思想剖析,更多可以参考这篇文章)
    在这里插入图片描述
  4. 调用runInLoop,在对应的EventLoop中执行相应的回调函数。但是这里存在一个问题,即我是通过MainLoop上所在的线程选择了一个子Loop,假如说为subLoop1,并且需要执行在subLoop1上绑定的函数,而当前的线程是MainLoop对应的线程。如果之间运行,这就不满足每一个线程运行一个EventLoop的条件了(one loop per thread)。所以在这里,就需要判断一下,是否是在当前loop中对应的线程执行的任务。如果不是,需要通过wakeup函数唤醒当前loop对应的线程。
  5. 然后loop对应的线程就被唤醒起来工作(相当于消费者来消费任务了),执行绑定的connectEstablished()函数,在connectEstablished()函数实现tie()函数绑定,并且设置监听读事件。

建立Tcp连接后,以后发生在这个连接上的所有事件都交由这个SubLoop1来负责了。

有消息需要发送时(多reactor情况时)

在这里插入代码片

关闭连接时

当连接断开或者关闭连接,执行TcpServer::removeConnection()回调函数。
在这里插入图片描述

  1. 上层调用TcpServer::removeConnection函数,调用runInLoop函数,并绑定执行TcpServer::removeConnectionInLoop
  2. 因为removeConnectionInLoop是执行在mainloop对应的线程(执行TcpServer类下的函数都在mainloop中对应的线程,也可以说在主线程中运行),所以这里之间是在当前的mainloop中执行回调,执行removeConnectionInLoop函数
  3. 首先移除TCP连接,并且得到将要移除的连接对应的ioloop;因为当前是处于mainloop对应的主线程,所以ioloop执行调用queueInLoop
  4. queueInLoop函数中,核心是异步唤醒自己对应的线程,并执行等待执行的任务(在这里是connectDestroyed)
  5. connectDestroyed中,将待移除的TCP连接中的channel中的所有感兴趣的事件从poller中移除掉,并将channel从poller中移除。

子线程被唤醒执行任务

我们知道,在线程池初始化过程中,每一个线程就对应一个事件循环(eventloop)已开始循环,并且当新连接到来时,mainloop按照轮询方法将新连接分发给一个事件循环,以后这个事件循环就负责这个新连接的所有操作了(包括接收消息、发送消息等)。

void EventLoop::loop()
{looping_ = true;quit_ = false;LOG_INFO("EventLoop %p start looping\n", this);while (!quit_){activeChannels_.clear();pollRetureTime_ = poller_->poll(kPollTimeMs, &activeChannels_);for (Channel *channel : activeChannels_){// Poller监听哪些channel发生了事件 然后上报给EventLoop 通知channel处理相应的事件channel->handleEvent(pollRetureTime_);}doPendingFunctors();//执行等待处理的函数,一般是主线程添加的。}LOG_INFO("EventLoop %p stop looping.\n", this);looping_ = false;
}

可以看到,初始化的事件循环eventloop在自己的线程内循环等待任务的来临。

pollRetureTime_ = poller_->poll(kPollTimeMs, &activeChannels_);

这句话是监听对于每一个连接注册的事件是否发生了(如果发生了,注册到activeChannels_并依序执行);否则超时返回。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI智能体“上下文工程”实践:来自 Manus 项目的经验总结

转载&#xff1a;https://manus.im/blog/Context-Engineering-for-AI-Agents-Lessons-from-Building-Manus 在启动 Manus (manus.im/app) 项目之初&#xff0c;我的团队面临一个关键抉择&#xff1a;究竟是基于开源基础模型训练一个端到端的智能体模型&#xff0c;还是在前沿大…

day19 链表

定义链式存储的线性表头文件相关定义 typedef int datatype;//定义数据域类型 typedef struct Node {union{int len; //头结点数据域datatype data; //普通节点数据域};struct Node *next; //节点指针域 }Node,*Node_ptr;链表的函数 注意事项 1.创建节点时&#xff0c;需要初…

【第三节】Class与Style绑定

文章目录Class与Style绑定绑定HTML Class对象语法数组语法绑定内联样式对象语法数组语法自动添加前缀Class与Style绑定 数据绑定一个常见需求是操作元素的 class 列表和它的内联样式,因为它们都是属性&#xff0c;我们可以用 v-bind 处理它们:我们只需要计算出表达式最终的字符…

CMOS知识点 离子注入工艺

知识点8&#xff1a;离子注入是为了将掺杂剂&#xff08;如硼、磷等&#xff09;精确引入硅晶片的近表面区域&#xff0c;以改变其电学性质。工艺过程&#xff1a;电离与加速&#xff1a;掺杂剂原子在离子源中被电离&#xff08;带电&#xff09;&#xff0c;通过高压电场&…

从安装到上手:Ubuntu 22.04 玩转 Containerd 2.1.3 容器运行时

Containerd 是一款支持 OCI 规范的容器运行时&#xff0c;注重容器部署和生命周期管理的简单性、健壮性与可移植性&#xff0c;常被嵌入到 Docker 和 Kubernetes 等系统中。本文将详细介绍在 Ubuntu 22.04 服务器上通过二进制包手动安装 Containerd 的完整步骤&#xff0c;包括…

Hadoop与云原生集成:弹性扩缩容与OSS存储分离架构深度解析

Hadoop与云原生集成的必要性Hadoop在大数据领域的基石地位作为大数据处理领域的奠基性技术&#xff0c;Hadoop自2006年诞生以来已形成包含HDFS、YARN、MapReduce三大核心组件的完整生态体系。根据CSDN技术社区的分析报告&#xff0c;全球超过75%的《财富》500强企业仍在使用Had…

飞算科技:以创新科技引领数字化变革,旗下飞算 JavaAI 成开发利器

作为国家级高新技术企业&#xff0c;飞算科技专注于自主创新&#xff0c;在数字科技领域持续深耕&#xff0c;用前沿技术为各行业客户赋能&#xff0c;助力其实现数字化转型升级的飞跃。​飞算科技凭借深厚的技术积累&#xff0c;将互联网科技、大数据、人工智能等技术与实际应…

多线程Python爬虫:加速大规模学术文献采集

1. 引言 在学术研究过程中&#xff0c;高效获取大量文献数据是许多科研工作者和数据分析师的需求。然而&#xff0c;传统的单线程爬虫在面对大规模数据采集时&#xff0c;往往效率低下&#xff0c;难以满足快速获取数据的要求。因此&#xff0c;利用多线程技术优化Python爬虫&a…

NX717NX720美光固态闪存NX724NX728

美光NX系列固态闪存深度解析&#xff1a;技术、性能与市场洞察一、技术架构与核心创新美光NX系列固态闪存&#xff08;包括NX717、NX720、NX724、NX728&#xff09;的技术根基源于其先进的G9 NAND架构。该架构通过5纳米制程工艺和多层3D堆叠技术&#xff0c;实现了存储单元密度…

浅谈——C++和C#差异

虽然这个话题看着似乎有些关公战秦琼的味道&#xff0c;但是作为游戏开发者&#xff0c;C和C#一定是绕不开的两门语言。不过虽然说是比较二者差异&#xff0c;因为我学习的过程主要是先学C&#xff0c;所以我先基于C的认知&#xff0c;再来聊聊C#之中的不同。&#xff08;为什么…

rocky9-zabbix简单部署

目录 一、准备 1、&#xff08;rocky9&#xff09; 2、配置数据库 二、配置文件 1、导入初始架构与数据 2、配置相关文件 三、启动服务 1、浏览器访问 2、解决乱码问题 ​编辑 四、监控 ① 添加主机 1、修改配置文件 2、启动服务 3、网页添加 ②添加监控模块 1…

tabBar设置底部菜单选项、iconfont图标(图片)库、模拟京东app的底部导航栏

欢迎来到我的UniApp技术专栏&#xff01;&#x1f389; 在这里&#xff0c;我将与大家分享关于UniApp开发的实用技巧、最佳实践和项目经验。 专栏特色&#xff1a; &#x1f4f1; 跨平台开发一站式解决方案 &#x1f680; 从入门到精通的完整学习路径 &#x1f4a1; 实战项目经…

7.22总结mstp,vrrp

一、MSTP技术&#xfeff;&#xfeff;MSTI和MSTI域根&#xfeff;&#xfeff;MSTP中的端口角色3. MSTP工作原理 MSTP 计算方法• CST/IST的计算和RSTP类似 • MSTI的计算仅限于区域内 • MSTI计算参数包含在IST BPDU中&#xff0c;和IST的计 算同步完成&#xfeff;&#xfe…

【电脑】网卡的基础知识

网卡&#xff08;Network Interface Card, NIC&#xff09;是计算机中用于连接网络的关键组件之一&#xff0c;它负责管理和发送数据包到互联网或其他局域网设备。下面是一些关于网卡的详细知识&#xff1a;网卡的基本结构MAC地址&#xff1a;每个网卡都有一个唯一的物理地址&a…

IPv4枯竭时代:从NAT技术到IPv6的演进之路

&#x1f50d; 开发者资源导航 &#x1f50d;&#x1f3f7;️ 博客主页&#xff1a; 个人主页&#x1f4da; 专栏订阅&#xff1a; JavaEE全栈专栏 IPv4&#xff08;Internet Protocol version 4&#xff09;是互联网最核心的通信协议之一&#xff0c;自 1981 年正式标准化以来…

模式结构-微服务架构设计模式

需求&#xff08;Forces)结果上下文(Resulting context)相关模式(Related patterns)需求&#xff1a;必须解决的问题需求部分描述了必须解决的问题和围绕这个问题的特定上下文环境。需求有时候是相互冲突的&#xff0c;所以不能指望把他们全部都解决&#xff08;必须取舍&#…

30个常用的Linux命令汇总和实战场景示例

下面汇总常用的 30 个常用的 Linux 命令&#xff0c;每个都附有简要说明和典型示例&#xff0c;适合日常开发、服务器维护或系统学习使用。30 个常用的 Linux 命令汇总 一、文件与目录操作&#xff08;基础&#xff09;命令说明示例ls列出文件和目录ls -l 显示详细信息cd切换目…

Taro 网络 API 详解与实用案例

Taro 网络 API 详解与实用案例 在现代前端开发中&#xff0c;网络通信是不可或缺的一环。Taro 作为一款多端开发框架&#xff0c;提供了丰富且统一的网络 API&#xff0c;帮助开发者在小程序、H5、React Native 等多端环境下高效地进行数据交互。本文将详细介绍 Taro 的四大网…

Bitbucket平台的HTTP Access Tokens操作手册

在Bitbucket平台添加HTTP Access Tokens&#xff08;用于替代密码进行认证&#xff09;。 1. 登录Bitbucket并访问个人设置 打开 Bitbucket 并登录账号。点击右上角头像 → 选择 Manage account。 2. 生成Access Token 在左侧菜单中选择 Access tokens&#xff08;位于 Sec…

低成本、高泛化能力的无人机自主飞行!VLM-Nav:基于单目视觉与视觉语言模型的无地图无人机导航

作者&#xff1a;Gobinda Chandra Sarker1^{1}1, AKM Azad2^{2}2, Sejuti Rahman1^{1}1, Md Mehedi Hasan1^{1}1单位&#xff1a;1^{1}1达卡大学&#xff0c;2^{2}2伊玛目穆罕默德伊本沙特伊斯兰大学论文标题&#xff1a;VLM-Nav: Mapless UAV-Navigation Using Monocular Visi…