muduo网络库学习(八)事件驱动循环线程池EventLoopThreadPool
来源:互联网 发布:c语言的开发环境 编辑:程序博客网 时间:2024/05/22 15:59
muduo
是支持多线程的网络库,在muduo网络库学习(七)用于创建服务器的类TcpServer中也提及了TcpServer
中有一个事件驱动循环线程池,线程池中存在大量线程,每个线程运行一个EventLoop::loop
。
线程池的作用体现在
- 用户启动
TcpServer
服务器时创建大量子线程,每个子线程创建一个EventLoop
并开始执行EventLoop::loop
- 主线程的线程池保存着创建的这些线程和
EventLoop
- 当
Acceptor
接收到客户端的连接请求后返回TcpServer
,TcpServer
创建TcpConnection
用于管理tcp
连接 TcpServer
从事件驱动线程池中取出一个EventLoop
,并将这个EventLoop
传给TcpConnection
的构造函数TcpConnection
创建用于管理套接字的Channel
并注册到从线程池中取出的EventLoop
的Poller
中- 服务器继续监听
这个池子既是一个线程池,又是一个EventLoop
池,二者是等价的,一个EventLoop
对应一个线程
这种方式称为one loop per thread
即reactor + 线程池
线程池的定义比较简单,唯一复杂的地方是由主线程创建子线程,子线程创建EventLoop
并执行EventLoop::loop
,主线程返回创建的EventLoop
给线程池并保存起来,比较绕。
线程池EventLoopThreadPool
定义如下
class EventLoop;class EventLoopThread;class EventLoopThreadPool : noncopyable{ public: typedef std::function<void(EventLoop*)> ThreadInitCallback; EventLoopThreadPool(EventLoop* baseLoop, const string& nameArg); ~EventLoopThreadPool(); void setThreadNum(int numThreads) { numThreads_ = numThreads; } /* 开启线程池,创建线程 */ void start(const ThreadInitCallback& cb = ThreadInitCallback()); // valid after calling start() /// round-robin /* 获取一个线程(事件驱动循环),通常在创建TcpConnection时调用 */ EventLoop* getNextLoop(); /// with the same hash code, it will always return the same EventLoop EventLoop* getLoopForHash(size_t hashCode); std::vector<EventLoop*> getAllLoops(); bool started() const { return started_; } const string& name() const { return name_; } private: /* 主线程的事件驱动循环,TcpServer所在的事件驱动循环,创建TcpServer传入的EventLoop */ EventLoop* baseLoop_; string name_; bool started_; /* 线程数 */ int numThreads_; /* 标记下次应该取出哪个线程,采用round_robin */ int next_; /* 线程池中所有的线程 */ std::vector<std::unique_ptr<EventLoopThread>> threads_; /* * 线程池中每个线程对应的事件驱动循环,从线程池取出线程实际上返回的是事件驱动循环 * 每个事件驱动循环运行在一个线程中 */ std::vector<EventLoop*> loops_;};
成员变量和函数没什么特别的,其中
baseLoop_
是主线程所在的事件驱动循环,即TcpServer
所在的那个主线程,这个事件驱动循环通常只负责监听客户端连接请求,即Acceptor
的Channel
。- 两个
vector
保存着所有子线程即每个子线程对应的EventLoop
。事件驱动循环线程被封装在EventLoopThread
中,EventLoopThread
中使用的Thread
才是真正的线程封装
线程池是由TcpServer
启动的,在TcpServer::start
函数中(由用户调用)
/* * 开启事件驱动循环线程池,将Acceptor的Channel添加到EventLoop中,注册到Poller上 * 此时还没有调用EventLoop::loop(),所以还没有开启监听 */void TcpServer::start(){ if (started_.getAndSet(1) == 0) { /* 启动线程池 */ threadPool_->start(threadInitCallback_); assert(!acceptor_->listenning()); /* * Acceptor和TcpServer在同一个线程,通常会直接调用 * std::bind只能值绑定,如果传入智能指针会增加引用计数,这里传递普通指针 * 因为TcpServer没有销毁,所以不用担心Accepor会销毁 */ loop_->runInLoop( std::bind(&Acceptor::listen, get_pointer(acceptor_))); }}
TcpServer::start
中调用threadPool_->start()
用于启动线程池,传入的参数是创建好所有线程后调用的回调函数,也是由用户提供
void EventLoopThreadPool::start(const ThreadInitCallback& cb){ assert(!started_); baseLoop_->assertInLoopThread(); started_ = true; /* 创建一定数量的线程(事件驱动循环) */ for (int i = 0; i < numThreads_; ++i) { char buf[name_.size() + 32]; snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i); /* EventLoopThread,事件驱动循环线程*/ EventLoopThread* t = new EventLoopThread(cb, buf); threads_.push_back(std::unique_ptr<EventLoopThread>(t)); /* 创建新线程,返回新线程的事件驱动循环EventLoop */ loops_.push_back(t->startLoop()); } if (numThreads_ == 0 && cb) { cb(baseLoop_); }}
创建线程的过程有些绕,在这里梳理一下
- 线程池所在线程是主线程,所有通过
pthread_create
创建的线程为子线程 - 创建线程首先构造
EventLoopThread
对象,这里保存着一个事件驱动循环loop_和线程Thread,但是事件驱动循环初始为null
,Thread
初始时设置了回调函数,在创建完线程后执行 - 执行
EventLoopThread::startLoop
函数启动Thread创建子线程,主线程返回阻塞在loop_
上因为它为null
,子线程执行回调函数创建EventLoop
并赋值给loops_
,同时唤醒主线程 - 主线程返回
loops_
,子线程执行EventLoop::loop
开始监听事件
/* * 线程池所在线程在每创建一个EventLoopThread后会调用相应对象的startLoop函数,注意主线程和子线程之分 * 主线程是TcpServer所在线程,也是线程池所在线程 * 子线程是由线程池通过pthread_create创建的线程,每一个子线程运行一个EventLoop::loop * * 1.主线程EventLoopThreadPool创建EventLoopThread对象 * 2.主线程EventLoopThread构造函数中初始化线程类Thread并传递回调函数EventLoopThread::threadFunc * 3.主线程EventLoopThreadPool创建完EventLoopThread后,调用EventLoopThread::startLoop函数 * 4.主线程EventLoopThread::startLoop函数开启线程类Thread,即调用Thread::start * 5.主线程Thread::start函数中使用pthread_create创建线程后 * 子线程调用回调函数EventLoopThread::threadFunc,主线程返回到EventLoopThread::startLoop * 6.主线程EventLoopThread::startLoop由于当前事件驱动循环loop_为null(构造时初始化为null)导致wait * 7.子线程EventLoopThread::threadFunc创建EventLoop并赋值给loop_,然后唤醒阻塞在cond上的主线程 * 8.主线程EventLoopThread::startLoop被唤醒后,返回loop_给EventLoopThreadPool * 9.主线程EventLoopThreadPool保存返回的loop_,存放在成员变量std::vector<EventLoop*> loops_中 * 10.子线程仍然在threadFunc中,调用EventLoop::loop函数,无限循环监听 */EventLoop* EventLoopThread::startLoop(){ assert(!thread_.started()); /* 主线程调用线程类的start函数,创建线程 */ thread_.start(); { /* 加锁,原因是loop_可能会被子线程更改 */ MutexLockGuard lock(mutex_); /* * 如果loop_仍然为null,说明子线程那边还没有进入threadFunc创建EventLoop,wait等待 * pthread_cond_wait(pthread_cond_t&, pthread_mutex_t&);会自动解锁,然后睡眠 * 等待某个线程使用pthread_cond_signal(&pthread_cond_t&);或pthread_cond_boardcast(pthread_cond_t&) * 唤醒wait的一个/全部线程 * 当主线程从wait中返回后,子线程已经创建了EventLoop,主线程返回到EventLoopThreadPool中 * 子线程执行EventLoop::loop函数监听事件 */ while (loop_ == NULL) { cond_.wait(); } } return loop_;}
Thread类通过调用pthread_create创建子线程
/* EventLoopThread::startLoop函数中调用,用于创建子线程 */void Thread::start(){ assert(!started_); started_ = true; // FIXME: move(func_) /* 创建子线程为线程函数提供的参数,封装起来就可以实现传递多个参数 */ detail::ThreadData* data = new detail::ThreadData(func_, name_, &tid_, &latch_); /* 创建线程,子线程调用detail::startThread,主线程继续向下执行 */ if (pthread_create(&pthreadId_, NULL, &detail::startThread, data)) { started_ = false; delete data; // or no delete? LOG_SYSFATAL << "Failed in pthread_create"; } else { /* 如果线程创建成功,主线程阻塞在这里 */ latch_.wait(); assert(tid_ > 0); }}
创建成功后,主线程阻塞在latch_.wait()
上(条件变量),等待子线程执行threadFunc
之前被唤醒
这里不太明白原因,主线程为什么需要阻塞在这里
ThreadData是线程数据类,将线程函数用到的所有参数都存在这里面即可,线程函数为detail::startThread,进而调用runInThread
/* 创建线程后调用的函数,data是参数 */void* startThread(void* obj){ ThreadData* data = static_cast<ThreadData*>(obj); data->runInThread(); delete data; return NULL;}
runInLoop
调用latch_->countDown
,此时会唤醒主线程,主线程回到startLoop
中由于loop_
为null
阻塞在wait
上。而此时子线程正在准备调用threadFunc
(在EventLoopThread
创建之初将EventLoopThread::threadFunc
传递给Thread
,用于在创建完线程后调用)
/* * 创建线程后间接调用的函数,用于执行EventLoopThread::threadFunc * 这个函数在EventLoopThread构造时传给Thread对象的 * EventLoopThread::startLoop函数中调用Thread对象的Thread::start函数 * Thread::start中创建子线程,子线程调用detail::startThread,进而调用detail::runInThread * detail::runInLoop调用EventLoopThread::threadFunc,创建EventLoop,唤醒主线程,子线程执行loop循环 * 转了一大圈又回到EventLoopThread中 */ void runInThread() { *tid_ = muduo::CurrentThread::tid(); tid_ = NULL; latch_->countDown(); latch_ = NULL; muduo::CurrentThread::t_threadName = name_.empty() ? "muduoThread" : name_.c_str(); /* 给当前线程命名 */ ::prctl(PR_SET_NAME, muduo::CurrentThread::t_threadName); try { /* EventLoopThread::threadFunc() */ func_(); muduo::CurrentThread::t_threadName = "finished"; } catch (const Exception& ex) { muduo::CurrentThread::t_threadName = "crashed"; fprintf(stderr, "exception caught in Thread %s\n", name_.c_str()); fprintf(stderr, "reason: %s\n", ex.what()); fprintf(stderr, "stack trace: %s\n", ex.stackTrace()); abort(); } catch (const std::exception& ex) { muduo::CurrentThread::t_threadName = "crashed"; fprintf(stderr, "exception caught in Thread %s\n", name_.c_str()); fprintf(stderr, "reason: %s\n", ex.what()); abort(); } catch (...) { muduo::CurrentThread::t_threadName = "crashed"; fprintf(stderr, "unknown exception caught in Thread %s\n", name_.c_str()); throw; // rethrow } }
兜兜转转又回到了EventLoopThread
,此时主线程阻塞在EventLoopThread::startInLoop
的wait
上,子线程在EventLoopThread::threadFunc
中,准备创建一个EventLoop
然后唤醒主线程,并开启事件循环
/* * 传递给线程的回调函数,当创建线程后,在detail::runInLoop会回调这个函数 * 此函数创建一个事件驱动循环,并开启事件监听(loop) */void EventLoopThread::threadFunc(){ /* 子线程创建事件驱动循环 */ EventLoop loop; if (callback_) { callback_(&loop); } { /* 上锁后赋值给loop_ */ MutexLockGuard lock(mutex_); loop_ = &loop; /* * pthread_cond_signal(pthread_cond_t&)唤醒一个wait的线程 * 此时主线程发现loop_已经不为null,随后返回到EventLoopThreadPool中 */ cond_.notify(); } /* 子线程开启事件监听,进入无限循环,不返回 */ loop.loop(); //assert(exiting_); loop_ = NULL;}
子线程将一直停留在loop.loop()
上,主线程由于被子线程唤醒,发现loop_
已经不为null
,说明已经创建了一个线程,同时也在那个线程中创建了一个事件驱动循环,所以主线程返回,将创建好的事件驱动循环返回给线程池保存起来,当有新的TcpConnection
被创建后取出一个用来监听tcp
连接
void EventLoopThreadPool::start(const ThreadInitCallback& cb){ assert(!started_); baseLoop_->assertInLoopThread(); started_ = true; /* 创建一定数量的线程(事件驱动循环) */ for (int i = 0; i < numThreads_; ++i) { char buf[name_.size() + 32]; snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i); /* EventLoopThread,事件驱动循环线程*/ EventLoopThread* t = new EventLoopThread(cb, buf); threads_.push_back(std::unique_ptr<EventLoopThread>(t)); /* 创建新线程,返回新线程的事件驱动循环EventLoop */ /* EventLoopThread主线程返回后,将事件驱动循环保存下来,然后继续创建线程 */ loops_.push_back(t->startLoop()); } if (numThreads_ == 0 && cb) { cb(baseLoop_); }}
至此线程池的创建工作完成,每一个线程都运行着EventLoop::loop
,进行EventLoop::loop -> Poller::poll -> Channel::handleEvent -> TcpConnection::handle* -> EventLoop::doPendingFunctors -> EventLoop::loop
的工作。
如果提供了回调函数,在创建完成后也会执行,但通常用户不会在意线程池的创建工作,所以一般都不提供
创建完成后,线程池唯一的工作就是在新建TcpConnection
时从池子中取出一个EventLoop
传给TcpConnection
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr){ loop_->assertInLoopThread(); /* 从事件驱动线程池中取出一个线程给TcpConnection */ EventLoop* ioLoop = threadPool_->getNextLoop(); /* 为TcpConnection生成独一无二的名字 */ char buf[64]; snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_); ++nextConnId_; string connName = name_ + buf; LOG_INFO << "TcpServer::newConnection [" << name_ << "] - new connection [" << connName << "] from " << peerAddr.toIpPort(); /* * 根据sockfd获取tcp连接在本地的<地址,端口> * getsockname(int fd, struct sockaddr*, int *size); */ InetAddress localAddr(sockets::getLocalAddr(sockfd)); // FIXME poll with zero timeout to double confirm the new connection // FIXME use make_shared if necessary /* 创建一个新的TcpConnection代表一个Tcp连接 */ TcpConnectionPtr conn(new TcpConnection(ioLoop, connName, sockfd, localAddr, peerAddr)); ...}
EventLoopThreadPool::getNextLoop
函数如下,用于取出一个EventLoop
。
如果线程池中没有线程,就返回主线程的EventLoop
,此时只有一个EventLoop
在运行,即TcpServer
的那个
/* 从线程池中取出一个线程,挨着取 */EventLoop* EventLoopThreadPool::getNextLoop(){ baseLoop_->assertInLoopThread(); assert(started_); /* 线程池所在线程,TcpServer的主线程 */ EventLoop* loop = baseLoop_; /* * 如果不为空,取出一个 * 如果为空,说明线程池中没有创建线程,是单线程程序,返回主线程 */ if (!loops_.empty()) { // round-robin /* loops_保存所有的线程 */ loop = loops_[next_]; ++next_; if (implicit_cast<size_t>(next_) >= loops_.size()) { next_ = 0; } } return loop;}
- muduo网络库学习(八)事件驱动循环线程池EventLoopThreadPool
- muduo库阅读(36)——Net部分:事件循环线程池EventLoopThreadPool
- muduo网络库学习(四)事件驱动循环EventLoop
- muduo网络库学习之EventLoop(四):EventLoopThread 类、EventLoopThreadPool 类
- Muduo网络库源码分析(四)EventLoopThread和EventLoopThreadPool的封装
- Muduo网络库源码分析(四)EventLoopThread和EventLoopThreadPool的封装
- muduo网络库学习之EventLoop(一):事件循环类图简介和muduo 定时器TimeQueue
- muduo网络库学习笔记(5):线程池的实现
- Muduo网络库源码分析(一) EventLoop事件循环(Poller和Channel)
- Muduo网络库源码分析(一) EventLoop事件循环(Poller和Channel)
- muduo::EventLoopThread、EventLoopThreadPool分析
- muduo网络库学习笔记(6):单例类(线程安全的)
- muduo网络库学习笔记(7):线程特定数据
- muduo库阅读(35)——Net部分:用于执行事件循环(EventLoop)的线程类EventLoopThread
- muduo网络库学习(二)对套接字和监听事件的封装Channel
- muduo网络库源码学习————线程池实现
- muduo网络库学习之EventLoop(二):进程(线程)wait/notify 和 EventLoop::runInLoop
- muduo网络库:线程之间的同步机制(使用eventfd函数,条件变量,线程池)
- 讯飞语音:组件未安装(错误码21002)
- Linux命令(压缩,解压tar)
- 占据ADAS市场75%的份额,Mobileye是如何做到的?
- 奥兰多枪击案发生后让你五分钟报平安的Safety Check是怎么实现的?
- IEEE数字感知主席:VR迎来了第二春,但技术仍很多空白
- muduo网络库学习(八)事件驱动循环线程池EventLoopThreadPool
- java的命名规范
- “互联网+”十大标杆城市揭晓 数字GDP成经济发展新动能
- 常见功能--应用评分
- 用做产品的思维拍电影:你期待小米出品的电影吗?
- 京东和亚马逊之间,仅仅隔着一封致股东信
- 微软262亿美金收购LinkedIn背后,两家的首脑是如何达成共识的?
- 模板小结
- ubuntu16.04 搭建 dns 服务器