博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(P29)muduo_base库源码分析:进程(线程)wait/notify
阅读量:4299 次
发布时间:2019-05-27

本文共 4508 字,大约阅读时间需要 15 分钟。

文章目录

1.进程(线程)wait/notify

  • 一个线程通知一个等待中的线程,方法有3个:

  • 方法1:pipe

    (1)fd[0]对应管道的读端,fd[1]对应管道的写端,fd[0]只能用于读,不能用于写,fd[1]只能用于写,不能用于读,这意味着管道是单向的
    (2)等待线程等待fd[0]的可读事件,通知线程只要往fd[1]写入一个数据,fd[0]就变的可读了,等待线程就获得通知,就被唤醒了;

  • 方法2:socketpair

    (1)也有一对fd,任意一个fd都是既可读也可写的,与管道的区别在于,它可以用于双向通信;

  • 方法3:eventfd

    (1)只有一个fd,等待线程关注fd的可读事件,通知线程只要往fd写入一个数据,等待线程就获得通知,就被唤醒了;
    (2)muduo的线程唤醒使用evenfd
    (3)eventfd 是一个比 pipe 更高效的线程间事件通知机制,一方面它比 pipe 少用一个 file descripor,节省了资源;另一方面,eventfd 的缓冲区管理也简单得多,全部“buffer” 只有定长8 bytes,不像 pipe 那样可能有不定长的真正 buffer。

  • 总结

    (1)上面的3个方法还可以用于进程间的等待通知,线程间的通知方法除了上述3种,还可以用条件变量。
    (2)条件变量与上述3方法区别在于,他们都是fd,可以使用I/O复用来管理(poll,epoll)

  • 类图

    一个EventLoop对象可以包含多个Channel对象,他们是聚合关系,EventLoop对象不负责Channel的生存期;
    Channel作为TcpConnection,Acceptor,Connector的成员,是组合的关系,Channel对象的生存期是由这些生存来控制的;
    eg:29\jmuduo\muduo\net\TimerQueue.h中的 Channel timerfdChannel_;
    在这里插入图片描述

  • eg:29\jmuduo\muduo\net\EventLoop.h

    29\jmuduo\muduo\net\EventLoop.cc

  • runInLoop()流程图

    若调用runInLoop()的线程是当前I/O线程,isInLoopThread()若是当前线程要执行回调任务,那么就直接同步的调用cb;
    若不是当前线程调用了runInLoop(),queueInLoop()那么把任务添加到线程的队列中,以便I/O线程能够执行该任务;
    在这里插入图片描述

  • EventLoop::runInLoop

// 在I/O线程中执行某个回调函数,该函数可以跨线程调用void EventLoop::runInLoop(const Functor& cb){
if (isInLoopThread()) {
// 如果是当前IO线程调用runInLoop,则同步调用回调函数(任务)cb cb(); } else {
// 如果是其它线程调用runInLoop,则异步地将cb添加到队列 //以便让EventLoop所对应的I/O线程来执行该回调函数 queueInLoop(cb); }}
  • EventLoop::queueInLoop
//将任务cb添加到队列中void EventLoop::queueInLoop(const Functor& cb){
{
MutexLockGuard lock(mutex_); pendingFunctors_.push_back(cb);//将任务添加到一个任务队列pendingFunctors中 }//其他线程往I/O线程中添加一个任务,那么就需要唤醒这个I/O线程,以便其能//及时地执行该任务 // 调用queueInLoop的线程不是当前IO线程需要唤醒 // 或者调用queueInLoop的线程是当前IO线程,并且此时正在调用pending functor,需要唤醒, if (!isInLoopThread() || callingPendingFunctors_) // 只有当前IO线程的事件回调中调用queueInLoop才不需要唤醒 if (!isInLoopThread() || callingPendingFunctors_) {
wakeup(); }}
  • EventLoop::loop
while (!quit_)  {
activeChannels_.clear(); pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);//返回通道activeChannels_ if (Logger::logLevel() <= Logger::TRACE) {
printActiveChannels(); } eventHandling_ = true; for (ChannelList::iterator it = activeChannels_.begin(); it != activeChannels_.end(); ++it) {
currentActiveChannel_ = *it; currentActiveChannel_->handleEvent(pollReturnTime_);//handleEvent处理这些事件 } currentActiveChannel_ = NULL; eventHandling_ = false; //事件处理完毕后,调用 doPendingFunctors();//向当前I/O线程添加回调任务,目的是让I/O //线程也能执行一些计算任务。因为I/O线程不是很繁忙的时候,会一直处于阻塞不工作的状态。 }
  • 队列queueInLoop()流程图

    (1)若不是当前I/O线程((!isInLoopThread() == true时),则需要wakeup,A线程要把任务cb添加到B线程(他是I/O线程)的任务队列中,那么cb需要能够及时的被B执行,所以需要唤醒B,以便它能够执行。
    B是I/O线程,它处于loop中,poll()唤醒它,以便它能够执行到doPendingFunctors(),
    (2)若调用queueInLoop是当前线程((!isInLoopThread() == false时),并且此时正在调用pending functor,需要唤醒。这种情况只可能是doPendingFunctors()中又调用了queueInLoop(),即:当前IO线程处于doPendingFunctors(),在这个函数内部又调用了queueInLoop(),也需要唤醒当前的IO线程poll,否则doPendingFunctors()都执行了完毕之后,回来到poll,但是doPendingFunctors()这里面又调用了queueInLoop(),又添加了个任务,你没办法唤醒它,就没有办法及时的处理。
    (3)只有IO线程的事件回调中调用queueInLoop才不需要唤醒,在handleEvent中调用queueInLoop,将cb添加到队列中是不需要唤醒的,因为handleEvent处理完毕之后,会跑到doPendingFunctors(),所以说就不需要唤醒了。
    在这里插入图片描述

  • doPendingFunctors

// 当调用该函数时,就处于调用这些回调任务的状态中void EventLoop::doPendingFunctors(){
//定义了一个空的向量 std::vector
functors; callingPendingFunctors_ = true; {
// 只保护这块内容的临界区 MutexLockGuard lock(mutex_); functors.swap(pendingFunctors_);//交换完毕后,pendingFunctors_就变成了空 } //pendingFunctors_的回调任务都放到了functors中 /* 下面的内容没有保护,这是为啥呢?(1)不是简单地在临界区内依次调用Functor,而是把回调列表swap到functors中,这样一方面减小了临界区的长度(意味着不会阻塞其它线程的queueInLoop(): 又把回调任务添加进了pendingFunctors_,因为已经将pendingFunctors_交换到functors,下面的代码此时是不会执行的,所以下面的代码不需要取加锁) 另一方面,也避免了死锁(因为Functor可能再次调用queueInLoop()) lock//若其位置在callingPendingFunctors_ = true;之下 lock(该lock来自queueInLoop()) unlock//若其位置在 callingPendingFunctors_ = false;之上 由于此时还没有unlock,中间的是没有办法获得lock锁,递归了就会处于死锁的状态(2)由于doPendingFunctors()调用的Functor可能再次调用queueInLoop(cb),这时,queueInLoop()就必须wakeup(),否则新增的cb可能就不能及时调用了(3)muduo没有在loop()函数中反复执行doPendingFunctors()直到pendingFunctors为空,这是有意的,否则IO线程可能陷入死循环,无法处理IO事件。因为doPendingFunctors()调用了functors(),又调用了queueInLoop()将任务添加进来,那么loop()中的doPendingFunctors()可能永远无法执行到空,所以没有必要将所有的任务执行完毕,只是将交换出来的任务执行完毕而已。 */ // 遍历functors列表,执行它 for (size_t i = 0; i < functors.size(); ++i) {
// functors回调任务函数 functors[i](); } callingPendingFunctors_ = false;}
  • eg:29\jmuduo\muduo\net\EventLoop.cc

    29\jmuduo\muduo\net\TimerQueue.cc

  • eg测试:29\jmuduo\tests\Reactor_test05.cc

    29\jmuduo\tests\CMakeLists.txt

  • 测试:

    在这里插入图片描述

转载地址:http://omiws.baihongyu.com/

你可能感兴趣的文章
Linux(SUSE 12)安装Tomcat
查看>>
Linux(SUSE 12)安装jboss4并实现远程访问
查看>>
Neutron在给虚拟机分配网络时,底层是如何实现的?
查看>>
netfilter/iptables全攻略
查看>>
Overlay之VXLAN架构
查看>>
Eclipse : An error occurred while filtering resources(Maven错误提示)
查看>>
在eclipse上用tomcat部署项目404解决方案
查看>>
web.xml 配置中classpath: 与classpath*:的区别
查看>>
suse如何修改ssh端口为2222?
查看>>
详细理解“>/dev/null 2>&1”
查看>>
suse如何创建定时任务?
查看>>
suse搭建ftp服务器方法
查看>>
centos虚拟机设置共享文件夹并通过我的电脑访问[增加smbd端口修改]
查看>>
文件拷贝(IFileOperation::CopyItem)
查看>>
springboot(三) 用druid连接mybatis
查看>>
springboot(四) 用mybatis-generator自动生成bean和dao
查看>>
springboot(五)读写分离,多个读库,Druid监控
查看>>
springboot(六)配置swagger2
查看>>
springboot(七)redis 实现session共享
查看>>
springboot(八)拦截器之验证登录
查看>>