muduoライブラリ学習の設計と実装10-マルチスレッドTcpServer

東陽の研究ノート

マルチスレッドのTcpServerは、EventLoopThreadPollクラスを使用します。

一、EventLoopThreadPoll

思考のスレッドごとに1つのループでマルチスレッドTcpServerを実現するための重要な手順event loop pollは、ループ内での選択からTcpConnectionの使用までの新しいTcpConnectionにあります。言い換えると:

  • マルチスレッドTcpServer独自の(与TcpConnection共享的)EventLoopは、新しい接続を受け入れるためにのみ使用され、新しい接続は他の(来自线程池)EventLoopsを使用してIOを実行します。
  • シングルスレッドのTcpServerのEventLoopは、TcpConnectionと共有されます。

muduoのイベントループツールはEventLoopThreadPoolクラスで表され、インターフェイスは次のとおりです。

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_;
};

2. 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_、IN)(removeConnectionをコールするので、二つの機能に分割され所以需要把他移到 TcpServerloop_スレッド(因为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のコードは、シングルスレッドの状況(ミューテックスさえも)のみを処理し、EventLoop :: runInLoop()とEventLoopThreadPoolの導入により、マルチスレッドTcpServerの実装は簡単です。 。

  • 注:ioLoopとloop_の間のスレッドの切り替えは、再接続の確立時と切断時に発生します。これは、通常のサービスのパフォーマンスには影響しません。

3、スケジューリング方法

Muduoは現在、最も単純なラウンドロビンアルゴリズムを使用して、プール内のEventLoopを選択しています。

ラウンドロビンスケジューリング(ラウンドロビンスケジューリング)アルゴリズムは、ラウンドロビン方式で異なるサーバーへの要求を順番にスケジュールすることです。つまり、スケジューリングが実行されるたびに、i =(i + 1)mod n、およびi番目のサーバーです。が選択されています。このアルゴリズムの利点は、その単純さです。すべての接続の現在の状態を記録する必要がないため、ステートレススケジューリングです。

  • TcpConnectionは、動作中にEventLoopを置き換えることは許可されていません。これは、長接続サービスと短接続サービスの両方に適用でき、部分的な負荷を発生させるのは簡単ではありません。

3.1拡張:プール共有

muduoの現在の設計では、各TcpServerには独自のプールがあり、異なるTcpserver間で共有されることはありません。

  • 複数のTcpServerがEventLoopThreadPoolを共有できます。
  • もう1つの可能性は、2つのTcpServer(aおよびb)のEventLoop aLoopです。ここで、aはシングルスレッドサーバープログラムです。

おすすめ

転載: blog.csdn.net/qq_22473333/article/details/113755556