東陽の研究ノート
マルチスレッドの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をコールするので、二つの機能に分割され所以需要把他移到 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のコードは、シングルスレッドの状況(ミューテックスさえも)のみを処理し、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はシングルスレッドサーバープログラムです。