本文主要对比Muduo多线程模型方案8 和方案9 。

  方案8:reactor + thread pool ,有一个线程来充当reactor 接受连接分发事件,将要处理的事件分配给thread pool中的线程,由thread pool 来完成事件处理。实例代码见:examples/sudoku/server_threadpool.cc

  这里截取关键部分代码进行说明。

class SudokuServer
{
 public :
  SudokuServer(EventLoop* loop, const InetAddress& listenAddr, int numThreads)
    : loop_(loop),
      server_(loop, listenAddr, "SudokuServer"),
      numThreads_(numThreads),
      startTime_(Timestamp::now())
  {
    server_.setConnectionCallback(
        boost::bind(&SudokuServer::onConnection, this, _1));
    server_.setMessageCallback(
        boost::bind(&SudokuServer::onMessage, this, _1, _2, _3));
  }
 
  void start()
  {
    LOG_INFO << "starting " << numThreads_ << " threads.";
    threadPool_.start(numThreads_); // 注意这里,threadPool 的类型是: ThreadPool,且位置在start 里面
    server_.start();
  }
 
 private :
  void onConnection(const TcpConnectionPtr& conn)
  {
    LOG_TRACE << conn->peerAddress().toIpPort() << " -> "
        << conn->localAddress().toIpPort() << " is "
        << (conn->connected() ? "UP" : "DOWN");
  }
 
  void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp)
  {
...
        if (!processRequest(conn, request)) // 封装计算任务执行方法
        {
          conn->send( "Bad Request!\r\n");
          conn->shutdown();
          break;
        }
      }
...
    }
  }
 
  bool processRequest(const TcpConnectionPtr& conn, const string& request)
  {
...
 
    if (puzzle.size() == implicit_cast<size_t>(kCells))
    {
      threadPool_.run(boost::bind(&solve, conn, puzzle, id));// 将计算任务转移到 threadPool 线程
    }
    else
    {
      goodRequest = false;
    }
    return goodRequest;
  }
 
  static void solve(const TcpConnectionPtr& conn,
                    const string& puzzle,
                    const string& id)
  {
    LOG_DEBUG << conn->name();
    string result = solveSudoku(puzzle); // solveSudou 是一个pure function, 是可重入的 
    if (id.empty())
    {
      conn->send(result+ "\r\n");
    }
    else
    {
      conn->send(id+ ":"+result+ "\r\n");
    }
  }
 
  EventLoop* loop_;
  TcpServer server_;
  ThreadPool threadPool_; // 注意类型,方案8, reactor + threadpool
  int numThreads_;
  Timestamp startTime_;
};
 
void ThreadPool::start( int numThreads)  // 创建 thread pool,具体thread 调度这里暂时不分析
{
  assert(threads_.empty());
  running_ = true;
  threads_.reserve(numThreads);
  for (int i = 0; i < numThreads; ++i)
  {
    char id[32];
    snprintf(id, sizeof id, "%d", i);
    threads_.push_back( new muduo::Thread(
          boost::bind(&ThreadPool::runInThread, this), name_+id));
    threads_[i].start();
  }
}
 
方案9:main-reactor + subreactors, one loop per thread, 有一个主线程来扮演main-reactor 专门语句 accept 连接,其它线程负责读写文件描述符(socket)
 
class SudokuServer
{
 public :
  SudokuServer(EventLoop* loop, const InetAddress& listenAddr, int numThreads)
    : loop_(loop),
      server_(loop, listenAddr, "SudokuServer"),
      numThreads_(numThreads),
      startTime_(Timestamp::now())
  {
    server_.setConnectionCallback(
        boost::bind(&SudokuServer::onConnection, this, _1));
    server_.setMessageCallback(
        boost::bind(&SudokuServer::onMessage, this, _1, _2, _3));
    server_.setThreadNum(numThreads); // 设置 EventLoopThreadPool里面的thread数量
  }
 
  void start()
  {
    LOG_INFO << "starting " << numThreads_ << " threads.";
    server_.start();
  }
 
 private :
  void onConnection(const TcpConnectionPtr& conn)
  {
    LOG_TRACE << conn->peerAddress().toIpPort() << " -> "
        << conn->localAddress().toIpPort() << " is "
        << (conn->connected() ? "UP" : "DOWN");
  }
 
  void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp)
  {
...
        if (!processRequest(conn, request)) //准备计算
        {
          conn->send( "Bad Request!\r\n");
          conn->shutdown();
          break;
        }
...
    }
  }
 
  bool processRequest(const TcpConnectionPtr& conn, const string& request)
  {
...
    if (puzzle.size() == implicit_cast<size_t>(kCells))
    {
      LOG_DEBUG << conn->name();
      string result = solveSudoku(puzzle); // 计算在当前线程完成
      if (id.empty())
      {
        conn->send(result+ "\r\n");
      }
...
  }
  // 注意这里没有类型为ThreadPool的 threadPool_成员,整个类使用Muduo默认线程模型的EventLoopThreadPool,TcpServer 聚合了EventLoopThreadPool
  EventLoop* loop_;
  TcpServer server_;
  int numThreads_;
  Timestamp startTime_;
};
 
 
void TcpServer::setThreadNum( int numThreads)
{
  assert(0 <= numThreads);
  threadPool_->setThreadNum(numThreads); // 设置了 EventLoopThreadPool 里面的线程个数,为后面的threadPool_->start()服务
}
 
void TcpServer::start()
{
  if (!started_)
  {
    started_ = true;
    threadPool_->start(threadInitCallback_); // TcpServer 中的 threadPool 类型是 EventLoopThreadPool
  }
 
  if (!acceptor_->listenning())
  {
    loop_->runInLoop(
        boost::bind(&Acceptor::listen, get_pointer(acceptor_)));
  }
}
 
void EventLoopThreadPool::start( const ThreadInitCallback& cb) // 开启线程的方式是使用EventLoopThread,这个类将EventLoop 和 Thread 封装在一起实现 one loop per thread
{
  assert(!started_);
  baseLoop_->assertInLoopThread();
 
  started_ = true;
 
  for (int i = 0; i < numThreads_; ++i)
  {
    EventLoopThread* t = new EventLoopThread(cb); // 设置线程的 callback
    threads_.push_back(t); 
    loops_.push_back(t->startLoop()); // 保存loop方便管理和分配任务,任务分配其实是通过EventLoop::runInLoop() 来进行的
  }
  if (numThreads_ == 0 && cb)
  {
    cb(baseLoop_);
  }
}
 
  总结一下,这里所谓的Reactor就是持有Poller的结构(稍微有点狭隘,这里先就这样理解),Poller负责事件监听和分发。持有EventLoop的结构就持有Poller。
  对于方案8只有一个类持有EventLoop,也就是只创建了一个EventLoop,这个Loop就是reactor,其它的Thread 是通过ThreadPool来实现的,因此只有reactor所在的线程来完成I/O,其它线程用于完成计算任务,所以说这个模型适合于计算密集型而不是I/O密集型。
  对于方案9,存在多个Reactor,其中main reactor 持有Acceptor,专门用于监听三个半事件中的连接建立,消息到达和连接断开以及消息发送事件都让sub reactor来完成。由于main reactor 只关心连接建立事件,能够适应高并发的IO请求,多个subreactor的存在也能兼顾I/O与计算,因此被认为是一个比较好的方案。
   后面还会深入学习Muduo网络库相关的内容,包括Reactor结构的简化,线程池的实现,现代C++的编写方式,使用C++11进行重写等。现在看来C++11 thread library 提供的接口基本可以替换 posix thread library,虽然底层也许是通过posix thread实现的,毕竟Linux内核针对NPTL进行过修改。C++11 提供了 thread_local 来描述 线程局部存储,但是没有pthread_key_create() 提供 destructor那样的功能,或者遇到需要使用TLS的地方转过来使用posix 提供的接口。
 
Muduo 多线程 线程池 reactor

Muduo 多线程模型对比的更多相关文章

  1. Muduo 多线程模型:一个 Sudoku 服务器演变

    陈硕 (giantchen AT gmail) blog.csdn.net/Solstice Muduo 全系列文章列表: http://blog.csdn.net/Solstice/category ...

  2. Java NIO学习与记录(八): Reactor两种多线程模型的实现

    Reactor两种多线程模型的实现 注:本篇文章例子基于上一篇进行:Java NIO学习与记录(七): Reactor单线程模型的实现 紧接着上篇Reactor单线程模型的例子来,假设Handler的 ...

  3. 再谈多线程模型之生产者消费者(多生产者和单一消费者 )(c++11实现)

    0.关于 为缩短篇幅,本系列记录如下: 再谈多线程模型之生产者消费者(基础概念)(c++11实现) 再谈多线程模型之生产者消费者(单一生产者和单一消费者)(c++11实现) 再谈多线程模型之生产者消费 ...

  4. 再谈多线程模型之生产者消费者(单一生产者和多消费者 )(c++11实现)

    0.关于 为缩短篇幅,本系列记录如下: 再谈多线程模型之生产者消费者(基础概念)(c++11实现) 再谈多线程模型之生产者消费者(单一生产者和单一消费者)(c++11实现) 再谈多线程模型之生产者消费 ...

  5. 【转载】COM的多线程模型

    原文:COM的多线程模型 COM的多线程模型是COM技术里头最难以理解的部分之一,很多书都有涉及但是都没有很好的讲清楚.很多新人都会在这里觉得很迷惑,google大神能搜到一篇vckbase上的文章, ...

  6. Chrome多线程模型

    为什么使用多线程? Chrome的多线程模型主要解决什么问题? 如何实现该问题的解决? 1. 解决问题 Chrome有很多线程,这是为了保持UI线程(主线程)的高响应度,防止被其他费时的操作阻碍从而影 ...

  7. Oracle12c(12.1)中性能优化&amp;功能增强之通过参数THREADED_EXECTION使用多线程模型

    1.   后台 UNIX/Linux系统上,oracle用多进程模型.例如:linux上一个常规安装的数据库会有如下进程列: $ ps -ef | grep [o]ra_ oracle  15356  ...

  8. OS之进程管理---多线程模型和线程库(POSIX PTread)

    多线程简介 线程是CPU使用的基本单元,包括线程ID,程序计数器.寄存器组.各自的堆栈等,在相同线程组中,所有线程共享进程代码段,数据段和其他系统资源. 传统的的单线程模式是每一个进程只能单个控制线程 ...

  9. OSI七层模型和tcp/ip四层模型对比

    OSI 与TCP/IP 模型对比 OSI 协议层名称 TCP/IP 协议层名称 封装的单元 功能描述 TCP/IP协议 应用层(Application) 应用层(Application) 数据 应用程 ...

随机推荐

  1. 关于启明星系统(OA系统,预定系统,请假系统等)安全性的说明

    启明星系统推荐是安装在内网里,因此,系统采用了较为简单的身份验证. 事实上,在访问页面时,系统会判断当前cookie的uid是否大于0,如果是,则表示用户已经登录,否则表示未登录. public st ...

  2. 小白学数据分析----->留存率与运营活动分析_I

    有关留存率的事情最近扯得比较多,因为在分析数据的时候,越发觉得,分析一定是要来解决问题的,留存率不知何时突然变得流行了,在此讨论留存率倒不是因为流行,而是觉得以留存率为核心,的确是可以帮助我们解决不少 ...

  3. 【转】IT 圈里有哪些经常被读错的词?

    以下内容转至知乎,原文:http://www.zhihu.com/question/19739907?__nids__=5363833,5358751,5355121,5365018,5363846, ...

  4. WPF UI虚拟化

    ComboBox

  5. [AY技术分享]WPF AYUI的高大上日历代码

    看到这里,也谢谢大家关注了AYUI 这次讲的是AY最近没事开发的AyDatePicker,先看效果图 SelectMode=DateTime模式 SelectMode=OnlySelectDate模式 ...

  6. SK-Learn使用NMF(非负矩阵分解)和LDA(隐含狄利克雷分布)进行话题抽取

    英文链接:http://scikit-learn.org/stable/auto_examples/applications/topics_extraction_with_nmf_lda.html 这 ...

  7. 【转】开放api接口签名验证

    不要急,源代码分享在最底部,先问大家一个问题,你在写开放的API接口时是如何保证数据的安全性的?先来看看有哪些安全性问题在开放的api接口中,我们通过http Post或者Get方式请求服务器的时候, ...

  8. linux shell脚本备份mysql数据库

    #!/bin/sh # 备份数据库 # Mysql 用户名密码 MYSQL_USER=root MYSQL_PASS=root BACKUP_DIR=/data/backup/mysql DATA_D ...

  9. qt下调用win32api 修改分辨率

    在头文件中添加 #include <window.h> DEVMODE DevMode; EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, ...

  10. ch5 MySQL 备份与恢复

    第 5 章 MySQL 备份与恢复 前言 数据库的备份与恢复一直都是 DBA 工作中最为重要的部分之一,也是基本工作之一.任何正式环境的数据库都必须有完整的备份计划和恢复测试,本章内容将主要介绍 My ...