muduo库学习之设计与实现10——多线程TcpServer

东阳的学习笔记

多线程的 TcpServer,用到了 EventLoopThreadPoll class。

一、EventLoopThreadPoll

用 one loop per thread 的思想实现多线程 TcpServer 的关键步骤是在新建 TcpConnection 时从 event loop poll里挑选一个 loop 给 TcpConnection 用。也就是说:

  • 多线程 TcpServer 自己的(与TcpConnection共享的) EventLoop 只用来接受新连接,而新连接会用其他(来自线程池) EventLoop 来执行IO。
  • 而单线程 TcpServer 的 EventLoop 是与 TcpConnection 共享的.

muduo 的 event loop tool 由 EventLoopThreadPool class 表示,接口如下:

class EventLoopThreadPool : boost::noncopyable
{
 public:
  EventLoopThreadPool(EventLoop* baseLoop);
  ~EventLoopThreadPool();
  void setThreadNum(int numThreads) { numThreads_ = numThreads; }
  void start();
  EventLoop* getNextLoop();

 private:
  EventLoop* baseLoop_;
  bool started_;
  int numThreads_;
  int next_;  // always in loop thread
  boost::ptr_vector<EventLoopThread> threads_;
  std::vector<EventLoop*> loops_;
};

二、TcpServer 每次新建一个 TcpConnection

TcpServer 每次新建一个 TcpConnection 就会调用 getNextLoop() 来取得 EventLoop,如果是单线程服务,每次返回的都是 baseLoop_,即 TcpServer 自己用的那个 loop_。

  • 其中 setThreadNum() 的参数的意义见 TcpServer 代码注释。
  /// Set the number of threads for handling input.
  ///
  /// Always accepts new connection in loop's thread.
  /// Must be called before @c start
  /// @param numThreads
  /// - 0 means all I/O in loop's thread, no thread will created.
  ///   this is the default value.
  /// - 1 means all I/O in another thread.
  /// - N means a thread pool with N threads, new connections
  ///   are assigned on a round-robin basis.
  void setThreadNum(int numThreads);

TcpServer 只用增加一个成员函数和一个成员变量。

 private:
  /// Not thread safe, but in loop
  void newConnection(int sockfd, const InetAddress& peerAddr);
+ /// Thread safe.
  void removeConnection(const TcpConnectionPtr& conn);
+ /// Not thread safe, but in loop
+ void removeConnectionInLoop(const TcpConnectionPtr& conn);

  typedef std::map<std::string, TcpConnectionPtr> ConnectionMap;

  EventLoop* loop_;  // the acceptor loop
  const std::string name_;
  boost::scoped_ptr<Acceptor> acceptor_; // avoid revealing Acceptor
+ boost::scoped_ptr<EventLoopThreadPool> threadPool_;

2.1 TcpServer::newConnection()

多线程 TcpServer 的改动很简单,新建连接只改了 3 行代码。

  • 单线程时是把自己用的 loop_ 传给 TcpConnection;
  • 多线程是每次从 EventLoopThreadPool 取得 ioLoop
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
  loop_->assertInLoopThread();
  char buf[32];
  snprintf(buf, sizeof buf, "#%d", nextConnId_);
  ++nextConnId_;
  std::string connName = name_ + buf;

  LOG_INFO << "TcpServer::newConnection [" << name_
           << "] - new connection [" << connName
           << "] from " << peerAddr.toHostPort();
  InetAddress localAddr(sockets::getLocalAddr(sockfd));
  // FIXME poll with zero timeout to double confirm the new connection
+ EventLoop* ioLoop = threadPool_->getNextLoop();                            // 每次从线程池中获取线程,loops_[next_]
  TcpConnectionPtr conn(
!      new TcpConnection(ioLoop, connName, sockfd, localAddr, peerAddr));
  connections_[connName] = conn;
  conn->setConnectionCallback(connectionCallback_);
  conn->setMessageCallback(messageCallback_);
  conn->setWriteCompleteCallback(writeCompleteCallback_);
  conn->setCloseCallback(
      boost::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
!  ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn));
}

2.2 TcpServer::removeConnection()

多线程的连接的销毁也并不复杂,把原来的 removeConnection() 拆为两个函数,因为 TcpConnection 会在自己的 ioLoop_ 中调用 removeConnection(),所以需要把他移到 TcpServer 的 loop_ 线程(因为TcpServer 是无锁的

  • &14再次把 connectDestroyed() 移到 TcpConnection 的 ioLoop_ 线程进行,是为了保证 TcpConnection 的 ConnectionCallback 始终在其 ioLoop调用,方便客户端的编写
void TcpServer::removeConnection(const TcpConnectionPtr& conn)
{
+  // FIXME: unsafe
+  loop_->runInLoop(boost::bind(&TcpServer::removeConnectionInLoop, this, +conn));
}

void TcpServer::removeConnectionInLoop(const TcpConnectionPtr& conn)
+{
  loop_->assertInLoopThread();
!  LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_
           << "] - connection " << conn->name();
  size_t n = connections_.erase(conn->name());
  assert(n == 1); (void)n;
+  EventLoop* ioLoop = conn->getLoop();
!  ioLoop->queueInLoop(
      boost::bind(&TcpConnection::connectDestroyed, conn));
}

总而言之、TcpServer 和 TcpConnection 的代码都只处理单线程的情况(甚至都没有 mutex ),而借助 EventLoop::runInLoop() 并引入 EventLoopThreadPool让多线程 TcpServer 的实现易如反掌。

  • 注意:ioLoop 和 loop_ 间的线程切换都发生再连接建立和断开的时刻,不影响正常业务的性能。

三、调度方法

muduo目前采用最简单的 round-robin 算法来选取 pool 中的 EventLoop。

轮询调度(Round Robin Scheduling)算法就是以轮询的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。算法的优点是其简洁性,它无需记录当前所有连接的状态,所以它是一种无状态调度。

  • 不允许 TcpConnection 在运行中更换 EventLoop,这对长连接和短连接服务都是适用的,不易造成偏载。

3.1 扩展:pool 共享

muduo 目前的设计是每个TcpServer 都有自己的 pool,不同Tcpserver之间不共享。

  • 可以让多个 TcpServer 共享一个 EventLoopThreadPool.
  • 另外一种可能是一个 EventLoop aLoop 供两个 TcpServer 使用(a 和 b),其中 a 是单线程服务程序。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东阳z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值