muduo的并发模型为one loop per thread+ threadpool。为了方便使用,muduo封装了EventLoop和Thread为EventLoopThread,为了方便使用线程池,又把EventLoopThread封装为EventLoopThreadPool。

所以这篇博文并没有涉及到新奇的技术。可是也有一些封装和逻辑方面的注意点须要我们去分析和理解。

EventLoopThread

不论什么一个线程,仅仅要创建并执行了EventLoop,就是一个IO线程。 EventLoopThread类就是一个封装好了的IO线程。

EventLoopThread的工作流程为: 

1、在主线程创建EventLoopThread对象。

2、主线程调用EventLoopThread.start(),启动EventLoopThread中的线程(称为IO线程),而且主线程要等待IO线程创建完毕EventLoop对象。 

3、IO线程调用threadFunc创建EventLoop对象。通知主线程已经创建完毕。 

4、主线程返回创建的EventLoop对象。

EventLoopThread.h

class EventLoopThread : boost::noncopyable
{
public:
typedef boost::function<void(EventLoop*)> ThreadInitCallback; EventLoopThread(const ThreadInitCallback& cb = ThreadInitCallback());
~EventLoopThread();
EventLoop* startLoop(); // 启动线程,该线程就成为了IO线程 private:
void threadFunc(); // 线程函数 EventLoop* loop_; // loop_指针指向一个EventLoop对象
bool exiting_;
Thread thread_;
MutexLock mutex_;
Condition cond_;
ThreadInitCallback callback_; // 回调函数在EventLoop::loop事件循环之前被调用
};

EventLoopThread.cc

EventLoopThread::EventLoopThread(const ThreadInitCallback& cb)
: loop_(NULL),
exiting_(false),
thread_(boost::bind(&EventLoopThread::threadFunc, this)),
mutex_(),
cond_(mutex_),
callback_(cb)
{
} EventLoopThread::~EventLoopThread()
{
exiting_ = true;
loop_->quit(); // 退出IO线程,让IO线程的loop循环退出。从而退出了IO线程
thread_.join(); //等待线程退出
} EventLoop* EventLoopThread::startLoop()
{
assert(!thread_.started());
thread_.start();//线程启动,调用threadFunc() {
MutexLockGuard lock(mutex_);
while (loop_ == NULL)
{
cond_.wait();//须要等待EventLoop对象的创建
}
} return loop_;
} void EventLoopThread::threadFunc()
{
EventLoop loop; if (callback_)
{
callback_(&loop);
} {
MutexLockGuard lock(mutex_);
// loop_指针指向了一个栈上的对象,threadFunc函数退出之后。这个指针就失效了
// threadFunc函数退出,就意味着线程退出了,EventLoopThread对象也就没有存在的价值了。
// 因而不会有什么大的问题
loop_ = &loop;
cond_.notify(); //创建好,发送通知
} loop.loop();// 会在这里循环,直到EventLoopThread析构。此后不再使用loop_訪问EventLoop了
//assert(exiting_);
}

測试程序:

#include <muduo/net/EventLoop.h>
#include <muduo/net/EventLoopThread.h> #include <stdio.h> using namespace muduo;
using namespace muduo::net; void runInThread()
{
printf("runInThread(): pid = %d, tid = %d\n",
getpid(), CurrentThread::tid());
} int main()
{
printf("main(): pid = %d, tid = %d\n",
getpid(), CurrentThread::tid()); EventLoopThread loopThread;
EventLoop* loop = loopThread.startLoop();
// 异步调用runInThread,即将runInThread加入到loop对象所在IO线程,让该IO线程运行
loop->runInLoop(runInThread);
sleep(1);
// runAfter内部也调用了runInLoop。所以这里也是异步调用
loop->runAfter(2, runInThread);
sleep(3);
loop->quit(); printf("exit main().\n");
}

对调用过程进行分析:(查看日志)

主线程调用 loop->runInLoop(runInThread);
因为主线程(不是IO线程)调用runInLoop。 故调用queueInLoop()
将runInThead
加入到队列,然后wakeup() IO线程。IO线程在doPendingFunctors()
中取loop->runAfter()
要唤醒一下,此时仅仅是运行runAfter()
加入了一个2s的定时器, 2s超时。timerfd_
可读,先handleRead()一下然后运行回调函数runInThread()。

那为什么exit main()
之后wakeupFd_
还会有可读事件呢?那是由于EventLoopThead
栈上对象析构,在析构函数内 loop_ ->quit(),
因为不是在IO线程调用quit(),故也须要唤醒一下。IO线程才干从poll
返回,这样再次循环推断 while (!quit_)
就能退出IO线程。

EventLoopThreadPool

muduo的线程模型:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

muduo的思想时eventLoop+thread pool。为了更方便使用,将EventLoopThread做了封装。main reactor能够创建sub reactor,并发一些任务分发到sub reactor中去。EventLoopThreadPool的思想比較简单,用一个main reactor创建EventLoopThreadPool。在EventLoopThreadPool中将EventLoop和Thread绑定,能够返回EventLoop对象来使用EventLoopThreadPool中的Thread。

EventLoopThreadPool.h

class EventLoopThreadPool : boost::noncopyable
{
public:
typedef boost::function<void(EventLoop*)> ThreadInitCallback; EventLoopThreadPool(EventLoop* baseLoop);
~EventLoopThreadPool();
void setThreadNum(int numThreads) { numThreads_ = numThreads; }
void start(const ThreadInitCallback& cb = ThreadInitCallback());
EventLoop* getNextLoop(); private: EventLoop* baseLoop_; // 与Acceptor所属EventLoop同样
bool started_;
int numThreads_; // 线程数
int next_; // 新连接到来。所选择的EventLoop对象下标
boost::ptr_vector<EventLoopThread> threads_; // IO线程列表
std::vector<EventLoop*> loops_; // EventLoop列表
};

EventLoopThreadPool.cc

EventLoopThreadPool::EventLoopThreadPool(EventLoop* baseLoop)
: baseLoop_(baseLoop),
started_(false),
numThreads_(0),
next_(0)
{
} EventLoopThreadPool::~EventLoopThreadPool()
{
// Don't delete loop, it's stack variable
} void EventLoopThreadPool::start(const ThreadInitCallback& cb)
{
assert(!started_);
baseLoop_->assertInLoopThread(); started_ = true; for (int i = 0; i < numThreads_; ++i)
{
EventLoopThread* t = new EventLoopThread(cb);
threads_.push_back(t);
loops_.push_back(t->startLoop()); // 启动EventLoopThread线程。在进入事件循环之前。会调用cb
}
if (numThreads_ == 0 && cb)
{
// 仅仅有一个EventLoop。在这个EventLoop进入事件循环之前,调用cb
cb(baseLoop_);
}
} EventLoop* EventLoopThreadPool::getNextLoop()
{
baseLoop_->assertInLoopThread();
EventLoop* loop = baseLoop_; // 假设loops_为空,则loop指向baseLoop_
// 假设不为空,依照round-robin(RR。轮叫)的调度方式选择一个EventLoop
if (!loops_.empty())
{
// round-robin
loop = loops_[next_];
++next_;
if (implicit_cast<size_t>(next_) >= loops_.size())
{
next_ = 0;
}
}
return loop;
}

mainReactor关注监听事件,已连接套接字事件轮询给线程池中的subReactors 处理,一个新的连接相应一个subReactor

我们採用round-robin(RR,轮叫)的调度方式选择一个EventLoop,也就是getNextLoop函数。极端情况下,线程池中个数为0时,那么新的连接交给mainReactor。这样就退化成单线程的模式。

Muduo网络库源代码分析(四)EventLoopThread和EventLoopThreadPool的封装的更多相关文章

  1. Muduo网络库源代码分析(六)TcpConnection 的生存期管理

    TcpConnection是使用shared_ptr来管理的类,由于它的生命周期模糊.TcpConnection表示已经建立或正在建立的连接.建立连接后,用户仅仅须要在上层类如TcpServer中设置 ...

  2. muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制

    目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...

  3. muduo网络库架构总结

    目录 muduo网络库简介 muduo网络库模块组成 Recator反应器 EventLoop的两个组件 TimerQueue定时器 Eventfd Connector和Acceptor连接器和监听器 ...

  4. 长文梳理muduo网络库核心代码、剖析优秀编程细节

    前言 muduo库是陈硕个人开发的tcp网络编程库,支持Reactor模型,推荐大家阅读陈硕写的<Linux多线程服务端编程:使用muduo C++网络库>.本人前段时间出于个人学习.找工 ...

  5. muduo 网络库学习之路(一)

    前提介绍: 本人是一名大三学生,主要使用C++开发,兴趣是高性能的服务器方面. 网络开发离不开网络库,所以今天开始学一个新的网络库,陈老师的muduo库 我参考的书籍就是陈老师自己关于muduo而编著 ...

  6. 陈硕 - Linux 多线程服务端编程 - muduo 网络库作者

    http://chenshuo.com/book/ Muduo网络库源码分析(一) EventLoop事件循环(Poller和Channel)http://blog.csdn.net/nk_test/ ...

  7. Muduo网络库实战(二):实现服务器与客户端的连接

    1. 方案的确定 1)基本需求 用户1000+, IO压力不大: 多个客户端打开网站,输入查询字符串strclient,发送给服务器=>服务器接收客户端发过来的数据并处理,将结果返回给客户端: ...

  8. muduo网络库使用心得

    上个月看了朋友推荐的mudo网络库,下完代码得知是国内同行的开源作品,甚是敬佩.下了mudo使用手冊和035版的代码看了下结构,感觉是一个比較成熟并且方便使用的网络库.本人手头也有自己的网络库,尽管不 ...

  9. muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor

    目录 muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor Connector 系统函数connect 处理非阻塞connect的步骤: Connetor时序图 Accep ...

随机推荐

  1. App Distribution Guide (一)

    This guide contains everything you need to know to distribute an app through the App Store or Mac Ap ...

  2. SimpleDateFormat关于时间类的一些常用处理

    项目中经常会出现对时间类的一些处理,记录一下: 实例一:/** * 获取当前时间是星期几? * * @param args */ public static void main(String[] ar ...

  3. Java6 WebService的发布

    Java6 WebService的发布   WebService服务发布往往比较混乱,Axis2的发布形式与XFire发布方式差别很大,而Java6 Web服务的发布与Axis2.XFire的Web服 ...

  4. Python 面向对象三(转载)

    来源:Mr.Seven www.cnblogs.com/wupeiqi/p/4766801.html 四.类的特殊成员 上文介绍了Python的类成员以及成员修饰符,从而了解到类中有字段.方法和属性三 ...

  5. 使用Java取得本机IP和机器名

    try { InetAddress addr = InetAddress.getLocalHost(); String ip=addr.getHostAddress().toString();//获得 ...

  6. Angular 学习笔记——$http

    <!DOCTYPE HTML> <html ng-app="myApp"> <head> <meta http-equiv="C ...

  7. iOS11

    _tab.estimatedRowHeight = 0; if (@available(iOS 11.0, *)) { //当有heightForHeader delegate时设置 _tab.est ...

  8. Shell脚本之:函数

    Shell 也支持函数.Shell函数必须先定义后使用. 函数的定义与调用 Shell 函数的定义格式如下: function_name () { list of commands [ return ...

  9. Oracle LOB字段判空

    dbms_lob.getlength() dbms_lob.getlength(null) 会报错--- Oracle 默认为clob字段插入empty_clob()

  10. 基于Android的rgb七彩环颜色采集器

    代码地址如下:http://www.demodashi.com/demo/11892.html 一.前言. 在大学期间,看到这个rgb灯,蛮好奇的,这么漂亮的颜色采集,并且可以同步到设备rbg灯颜色, ...