muduoソース分析-EventLoopThreadPoolとEventLoopThread


これからEventLoop関連のクラスを書きますが、EventLoopThreadPoolを先に書くことにしました.
TcpServerは所有関係から見るとEventLoopThreadPoolを持っているので、そしてEventLoopThreadPoolはいくつかのEventLoopThread(TcpServer初期化時に決定)を持っています.EventLoopを直接持っているかどうかについては、持っていると言ってもいいのではないかと思いますが、これらのEventLoopを操作していないので、管理器としか言いようがありません.プールを実現するというものです.
メンバー#メンバー#
	private:

		EventLoop*                                      baseLoop_;//      ,        
		std::string                                     name_;
		bool                                            started_;//  
		int                                             numThreads_;//   
		int                                             next_;//       
		std::vector<std::shared_ptr<EventLoopThread> >  threads_;//     
		std::vector<EventLoop*>                         loops_;//   EventLoop

baseLoop_メインEventLoopをロックするために使われていると思います.いつでもメインLoopを手に入れる必要があるかもしれませんが、後でEventLoop*EventLoopThreadPool::getNextLoop()を読むと
EventLoop* EventLoopThreadPool::getNextLoop()
{
	baseLoop_->assertInLoopThread();
    //assert(started_);
    if (!started_)
        return NULL;
	
	EventLoop* loop = baseLoop_;

	if (!loops_.empty())//        ,      ,       ,   TcpServer      ,    0,           
	{
		// round-robin
		loop = loops_[next_];//         ,       
		++next_;
		if (implicit_cast<size_t>(next_) >= loops_.size())//     0
		{
			next_ = 0;
		}
	}
	return loop;
}

実はもう一つの役割は、スレッドが1つのメインEventLoopしかなく、他の作業スレッドを初期化していない場合、このgetNextLoop()を正しく実行させることであり、この場合のために別の関数を書くことなく、プログラムの複雑さを低減したり、多重性を増やしたりすることである.ここでは,プライマリスレッドとワークスレッドが厳格に分離管理されていることがわかる.
他のメンバーについては、意味がはっきりしているので、あまり説明しません.
EventLoopThreadPool初期化およびstart
私は構造をinitやstartと一緒に置いておきましょうが、実は内容は多くなく複雑ではありません.
EventLoopThreadPool::EventLoopThreadPool()
: baseLoop_(NULL),
started_(false),
numThreads_(0),
next_(0)
{
}

EventLoopThreadPool::~EventLoopThreadPool()
{
	// Don't delete loop, it's stack variable
}

void EventLoopThreadPool::Init(EventLoop* baseLoop, int numThreads)
{
	numThreads_ = numThreads;
	baseLoop_ = baseLoop;
}

void EventLoopThreadPool::start(const ThreadInitCallback& cb)
{
    //assert(baseLoop_);
    if (baseLoop_ == NULL)
        return;
    
    assert(!started_);
    if (started_)//    start
        return;  
	
	baseLoop_->assertInLoopThread();

	started_ = true;

	for (int i = 0; i < numThreads_; ++i)
	{
		char buf[128];
		snprintf(buf, sizeof buf, "%s%d", name_.c_str(), i);//      ,       buf

		std::shared_ptr<EventLoopThread> t(new EventLoopThread(cb, buf));
		//EventLoopThread* t = new EventLoopThread(cb, buf);
		threads_.push_back(t);//         
		loops_.push_back(t->startLoop());//            loop EventLoop   ,     loops_ 
	}
	if (numThreads_ == 0 && cb)
	{
		cb(baseLoop_);//ThreadInitCallback,        
	}
}

構造的にはbaseLoop_デフォルトはNULLに初期化されていますが、実際にはTcpServerからEventLoopが渡され、startもデフォルトはfalseで、start、nextが0に初期化される必要があります.最初にタスクに追加されたのはもちろん0番目のスレッドなので、実はこの初期化は何でもいいと思います.負荷均衡が実現できるからです.しかし!防止のために、実際のスレッド数-1よりも大きい数に初期化したので、0を初期化した.そして後の私について説明する必要はないと思います.論理も難しくないからです.
EventLoopThread
次にEventLoopThreadというクラスを見てみましょう.
メンバー#メンバー#
	private:
		void threadFunc();

		EventLoop*                   loop_;//   EventLoop
		bool                         exiting_;//     
		std::shared_ptr<std::thread> thread_;//        
		std::mutex                   mutex_;//      
		std::condition_variable      cond_;//cond_.wait()          。    
		ThreadInitCallback           callback_;
	};

まず簡単に言えばexiting_この変数は、実はこのThreadが脱退したかどうかをマークしていますが、外部ではこの変数を引用する場所が見つからないので、具体的な用途はないかもしれません.純粋にマークとして使われているだけです.他のメンバーについてはコメントしていますが、次にEventLoopThreadの関数を見てみましょう.
startLoop
EventLoop* EventLoopThread::startLoop()
{
	//assert(!thread_.started());
	//thread_.start();

	thread_.reset(new std::thread(std::bind(&EventLoopThread::threadFunc, this)));//     

	{
		std::unique_lock<std::mutex> lock(mutex_);
		while (loop_ == NULL)
		{
			cond_.wait(lock);//     
		}
	}

	return loop_;
}
std::condition_variableを簡単に紹介します.ここでは、良いc++条件変数cond_を紹介します.wait()は、スレッドをスリープさせ、notify_を使用することができます.one()またはnotify_all()が起動し、もう一つはstd::lock_を使用できません.guardでstd::unique_を使用lock、具体的な原因は、上のリンクで説明したように、waitはunlock()を呼び出してから寝ます.std::lock_guardはこの実現はありませんよ.
この関数に戻ると、実は簡単です.新しいスレッドを開き、EventLoopThread::threadFuncを実行します.では、この関数を見てみましょう.
threadFunc
void EventLoopThread::threadFunc()
{
	EventLoop loop;

	if (callback_)
	{
		callback_(&loop);
	}

	{
		//         
        std::unique_lock<std::mutex> lock(mutex_);//       std::unique_lock  ,     unlock lock  
		loop_ = &loop;
		cond_.notify_all();
	}

	loop.loop();//    
	//assert(exiting_);
	loop_ = NULL;
}

ここのロックの論理を簡単に言えば、簡単に言えば、スレッドが起動した後、loop_すぐに!=NULLになって、最後にまたloop_=NULL、目的は前のstartLoop関数をブロックすることです.
正直このcallback、私はずっと具体的な実装を見つけていません.それはEventLoopThreadPoolに登録されていますが、TcpServerにstartを呼び出されたときに入ってきましたが、TcpServerがstartを呼び出すときにパラメータを渡したことがないので、ちょっと不思議ですが、この問題は解決しなければなりません.関数ポインタが伝わったと思いますが、このポインタは実装された関数ではなく、空です.後でブレークポイントを打って、ここが実行されるかどうか見てみましょう.
	if (callback_)
	{
		callback_(&loop);
	}

もう一度よく検討してください.前の内容に戻るとstartLoopがloop_を返しているのが見えますが、だからloopを待つ必要がある割り当てられてから戻ることができます.そうしないとNULLに戻るのは問題があります.loop_ = &loop;が実行された後、notify_が表示されます.all()プロセスを起動します.
stopLoop
void EventLoopThread::stopLoop()
{
    if (loop_ != NULL)
        loop_->quit();
 //   eventLoop quit_    true,          , eventLoop    

    thread_->join();//  this       ,        。
}

具体的なフラグを設定し、joinがクリーンアップし、EventLoopのquit_フラグがtrueの場合、EventLoopのwhileメインループは終了されますが、具体的にはEventLoopのコードを見てみましょう.見ればわかりますが、ここでは展開しません.まだEventLoopについて議論していないからです.そしてEventLoopのLoop関数が実行されると、join()でメモリをクリーンアップし、解放することができます.これが終了の方法です.私はjoinの紹介を探しました.ここでみんな見てもいいです.