0 前言

陈硕大佬的muduo网络库的源码我已经看了好久了,奈何本人实力有限,每每看到其代码设计的精巧之处只能内心称赞,无法用言语表达出来。实在令人汗颜。最近在看到网络设计部分时有了一些体会,结合自己之前在网络编程方面的积累,特对代码设计中的一些精巧之处做一些总结。
就muduo在多线程并发服务器设计而言,除了其高效的并发服务架构之外,其在代码设计方面的高效性和灵活性可以从下面三个切片得以体现。
在这之前,首先说明muduo的并发服务器架构为multi-reactors+thread pool架构,其架构图如下:
简单而言,mainReactor负责处理新连接IO的创建和管理,subReactor+thread pool负责已连接IO的读写事件。其中,为了降低subReactor的压力、提高IO读写效率,可以添加多个subReactor。

1 切片一:EventLoop线程绑定与跨线程调用

muduo_net设计中,每一个EventLoop对象对应一个reactor,并且每个EventLoop对象绑定一个线程,EventLoop对象中的loop()在内的多数函数只能由该对象所属的线程调用,即EventLoop对象有严格的线程所属特性。某些函数可以由外部线程调用,可以起到激活睡眠或阻塞的EventLoop当前线程的作用。
首先看一下EventLoop类的成员变量(部分):

  typedef std::vector<Channel*> ChannelList;bool looping_; /* atomic */bool quit_; /* atomic */bool eventHandling_; /* atomic */bool callingPendingFunctors_; /* atomic */const pid_t threadId_;       // 当前对象所属线程IDTimestamp pollReturnTime_;boost::scoped_ptr<Poller> poller_;boost::scoped_ptr<TimerQueue> timerQueue_;int wakeupFd_;               // 用于保存eventfd创建的file descriptor--进程间通信// unlike in TimerQueue, which is an internal class,// we don't expose Channel to client.// eventfd的wakeupFd_对应的通道 该通道将会纳入poller_来管理boost::scoped_ptr<Channel> wakeupChannel_;// EventLoop对象负责wakeupChannel_对象的创建/生命周期// Poller返回的活动通道    ChannelList activeChannels_;// 这些channel的生存期不由EventLoop管理和负责-->由TcpConnection和TimerQueue管理Channel* currentActiveChannel_;    // 当前正在处理的活动通道MutexLock mutex_;std::vector<Functor> pendingFunctors_; // @BuardedBy mutex_

具体体现如下。

1.1 判断当前线程是否已经拥有所属EventLoop对象

主要通过下面两条实现。
1 使用线程局部变量记录当前线程是否已经拥有所属EventLoop对象

// 当前线程EventLoop对象指针
// 线程局部存储
__thread EventLoop* t_loopInThisThread = 0;// 指向EventLoop对象

2 在创建EventLoop对象前进行判断

EventLoop::EventLoop(): looping_(false),quit_(false),eventHandling_(false),callingPendingFunctors_(false),threadId_(CurrentThread::tid()),poller_(Poller::newDefaultPoller(this)),timerQueue_(new TimerQueue(this)),wakeupFd_(createEventfd()),// 创建一个eventfdwakeupChannel_(new Channel(this, wakeupFd_)),// 创建一个通道 并将wawkeupFd_传进ChannelcurrentActiveChannel_(NULL)
{LOG_TRACE << "EventLoop created " << this << " in thread " << threadId_;/* 如果当前线程已经创建了EventLoop对象,终止(LOG_FATAL) */if (t_loopInThisThread){LOG_FATAL << "Another EventLoop " << t_loopInThisThread<< " exists in this thread " << threadId_;}else{t_loopInThisThread = this;// 指向EventLoop对象}// 绑定eventfd的回调处理函数handleRead()wakeupChannel_->setReadCallback(boost::bind(&EventLoop::handleRead, this));// we are always reading the wakeupfd// 绑定eventfd对应事件--EPOLLIN可读事件wakeupChannel_->enableReading();
}

1.2 执行函数前判断当前线程是否为EventLoop对象所属线程

通过assertInLoopThread()和isInLoopThread()函数进行判断。

  void assertInLoopThread(){if (!isInLoopThread()){abortNotInLoopThread();}}
// ...
bool isInLoopThread() const { return threadId_ == CurrentThread::tid(); }

1.3 部分函数可以跨线程调用以唤醒阻塞的EventLoop所属线程

以quit()函数为例,当要停止loop()循环时,此时EventLoop对象所属线程可能正处于poll的阻塞中,因此此时需要借助外部进程调用该quit()同时唤醒处于阻塞中的EventLoop对象所属线程,并退出poll循环。

// 该函数可以跨线程调用
void EventLoop::quit()
{quit_ = true;if (!isInLoopThread()){wakeup();}
}

2 切片二:IO线程与计算线程的灵活调度

为了充分了利用CPU,在IO线程空间时,可以为loop()线程分配一些计算任务。因此,muduo_net既可以完成IO处理又能进行计算任务。
具体如下。

2.1 poll+handleEvent

该部分主要负责IO事件的监听、响应和处理。

// 事件循环,该函数不能跨线程调用
// 只能在创建该对象的线程中调用
void EventLoop::loop()
{assert(!looping_);// 断言当前处于创建该对象的线程中assertInLoopThread();looping_ = true;quit_ = false;LOG_TRACE << "EventLoop " << this << " start looping";//::poll(NULL, 0, 5*1000);while (!quit_){activeChannels_.clear();/* IO事件监听、响应和处理 */pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);//++iteration_;if (Logger::logLevel() <= Logger::TRACE){printActiveChannels();}// TODO sort channel by priorityeventHandling_ = true;for (ChannelList::iterator it = activeChannels_.begin();it != activeChannels_.end(); ++it){currentActiveChannel_ = *it;currentActiveChannel_->handleEvent(pollReturnTime_);}currentActiveChannel_ = NULL;eventHandling_ = false;/* 处理计算任务 */doPendingFunctors();// 让IO线程不繁忙的时候也能执行一些计算任务-->避免IO线程一直处于阻塞状态}LOG_TRACE << "EventLoop " << this << " stop looping";looping_ = false;
}

2.2 queueInLoop+doPendingFunctors

该部分负责计算任务的添加和处理。其中添加计算任务queueInLoop可以由外部线程和EventLoop所属线程添加,执行计算任务doPendingFunctors只能由EventLoop线程处理。

// 将cb添加到队列中
void EventLoop::queueInLoop(const Functor& cb)
{{MutexLockGuard lock(mutex_);pendingFunctors_.push_back(cb);// 添加外部任务到pendingFunctors_数组}// 调用queueInLoop的线程不是IO线程需要唤醒(唤醒EventLoop对应的线程 以便该线程及时执行cb函数)// 或者调用queueInLoop的线程是EventLoop对应的IO线程,并且此时该线程正在调用pending functor(正在执行计算任务),需要唤醒// 只有IO线程的事件回调中调用queueInLoop才不需要唤醒if (!isInLoopThread() || callingPendingFunctors_){wakeup();}
}void EventLoop::doPendingFunctors()
{std::vector<Functor> functors;callingPendingFunctors_ = true;// 添加互斥锁// 互斥访问vector--此时pendingFunctors_位于临界区 不能被其他线程访问{MutexLockGuard lock(mutex_);functors.swap(pendingFunctors_);// swap:交换两容器的内容 pendingFunctors_变为空}// 为什么functors的执行没有放在临界区?// 1 减小临界区长度 减少其他进程queueInloop()的阻塞时间// 2 loop()线程发生IO事件时 可以及时中断该doPendingFunctors()函数中的计算任务,优先处理IO事件for (size_t i = 0; i < functors.size(); ++i){functors[i]();// 执行函数}callingPendingFunctors_ = false;
}

3 切片三:线程安全与执行效率

下面通过doPendingFunctors()函数观察muduo在实现线程安全方与保证执行效率之间权衡的设计。
该函数主要包含两个部分:获取待处理计算任务序列、依次执行计算任务。
其中pendingFunctors_存放计算任务的数组作为外部线程和EventLoop线程都能够处理(插入数据)的共享变量,EventLoop线程在获取计算任务序列时需要对pendingFunctors_进行加锁处理。
这其中涉及到锁的作用范围即临界区的作用范围的问题。muduo此处只将对pendingFunctors_的操作置于临界区,而将执行计算任务的操作置于临界区之外。原因主要有两个:
1 减小临界区长度 减少其他进程queueInloop()的阻塞时间;
2 loop()线程发生IO事件时 可以及时中断该doPendingFunctors()函数中的计算任务,优先处理IO事件。

void EventLoop::doPendingFunctors()
{std::vector<Functor> functors;callingPendingFunctors_ = true;/* 1 获取待处理计算任务序列 */// 添加互斥锁// 互斥访问vector--此时pendingFunctors_位于临界区 不能被其他线程访问(其他线程不能操作pendingFunctors_数组){MutexLockGuard lock(mutex_);functors.swap(pendingFunctors_);// swap:交换两容器的内容 pendingFunctors_变为空}/* 依次执行计算任务 */// 为什么functors的执行没有放在临界区?// 1 减小临界区长度 减少其他进程queueInloop()的阻塞时间// 2 loop()线程发生IO事件时 可以及时中断该doPendingFunctors()函数中的计算任务,优先处理IO事件for (size_t i = 0; i < functors.size(); ++i){functors[i]();// 执行函数}callingPendingFunctors_ = false;
}

4 参考材料

https://www.bilibili.com/video/BV11b411q7zr?p=29

muduo源码剖析——以三个切片浅析muduo库代码设计的严谨性、高效性与灵活性相关推荐

  1. UDT源码剖析(三):UDT::startup()过程代码注释

    调用路线 UDT::startup()->CUDT::startup()->CUDTUnited::startup() 1 int startup() 2 { 3    return CU ...

  2. 【Muduo源码剖析笔记】 网络库之Acceptor、Connector

    [Muduo源码剖析笔记] 网络库之Acceptor.Connector Acceptor typedef std::function<void (int sockfd, const InetA ...

  3. STL源码剖析(三)

    算法 从语言的角度看: 容器 Container 是一个class template 算法 Algorithm 是一个function template 迭代器 Iterator 是一个class t ...

  4. muduo源码剖析——Singleton单例模式之懒汉模式与DCL双重检查

    0 懒汉与饿汉 对于Singleton单例模式我们并不陌生,但我们常用的多是饿汉模式: Singleton实例的声明和实例化在instance()函数中同时完成. 而懒汉模式要求,Singleton实 ...

  5. Kafka源码剖析 —— 网络I/O篇 —— 浅析KafkaSelector

    为什么80%的码农都做不了架构师?>>>    ##NioSelector和KafkaSelector有什么区别? 先说结论,KafkaSelector(org.apache.kaf ...

  6. SpringMVC源码剖析(三)- DispatcherServlet的初始化流程

    我们启动web服务器,在浏览器中输入地址,就可以看到浏览器上输出我们写好的页面.为了更好的理解上面这个过程,你需要学习关于Servlet生命周期的三个阶段,就是所谓的"init-servic ...

  7. 3d稀疏卷积——spconv源码剖析(三)

    构建Rulebook 下面看ops.get_indice_pairs,位于:spconv/ops.py 构建Rulebook由ops.get_indice_pairs接口完成 get_indice_p ...

  8. 【开源项目学习】源码剖析,学习仿网易云音乐app代码

    [前言] 这篇文字不全是讲app代码,而是博主怎么根据代码系统学习梳理的过程,非专业,如有不对,欢迎指出 仿网易云音乐app源码地址:https://github.com/aa112901/remus ...

  9. redis源码剖析(三)——基础数据结构

    文章目录 SDS 链表 字典 这篇文章关于 Redis 的基础数据: SDS SDS (Simple Dynamic String)是 Redis 最基础的数据结构.直译过来就是"简单的动态 ...

最新文章

  1. 全奖博士 | 美国康涅狄格大学计算机科学与工程系
  2. 20145324 《信息安全系统设计基础》第十周学习总结
  3. 听说你想去大厂看妹子,带你看看腾讯产品运营实习面经
  4. centos7打开图形界面命令_centos7标准版(DVD)命令界面和图形界面相互切换
  5. 电脑桌面归纳小窗口_电脑一分钟小技巧:如何将电脑设置为定时关机?
  6. mac远程怎么操作?苹果电脑怎么远程协助?
  7. java多张图片合成一张_一款国外有趣、简单、功能齐全的图片处理软件。
  8. MATLAB如何输出高分辨率图片?
  9. mescroll下拉刷新上拉加载
  10. 微信小程序tabBar配置中的坑中坑
  11. 学习日志-《微习惯》心得
  12. sentinel 熔断降级
  13. 客户管理中如何管理好客户资料
  14. 怎么彻底关闭广告弹窗?
  15. P7395 弹珠游戏(2021 CoE-I C)
  16. 面试时,HR想坑我,没想到我社会经验这么足,竟然.....
  17. pcie总线与cpci总线_基于通用PCI接口功能芯片和热插拔控制器实现CPCI总线控制的设计...
  18. 今日头条投放广告价格
  19. hive中判断A表时间字段是否在B表的两个时间字段中及求订单中间休息时间
  20. 狂砸209亿美元筹码,ADI能否挑战德州仪器的“铁王座”?

热门文章

  1. ORA 12514 TNS listener does not currently know of service r
  2. 天合化工上市以来,成为股市模范,专家分析全球股市欺诈案慢慢减少原因
  3. XPS问题收集及解答
  4. Jmeter压力测试报告案例
  5. Python的对象和类型
  6. C语言实现,一球从100米高度自由落下,每次落地后反跳回原高度的一半;再落下,求它在第10次落地时,共经过多少米?第10次反弹多高?
  7. 01.java后台三层架构
  8. 消费心理学(01):心理账户
  9. 配置相应的chromedriver
  10. 《公路工程适应自动驾驶附属设施总体技术规范(征求意见稿)》公开征求意见...