天天看點

muduo 多線程的處理

一般網絡程式設計中為什麼會用到多線程呢,無非是充分利用伺服器多核的特性,提高網絡并發量,吞吐率等。一般情況下,我們可以輕松想到這樣的模型:主線程中監聽socket連接配接事件,當産生新的連接配接時生成新的線程來處理。這種方式比較直接,但是有些不好處理的地方,例如線程的數量,真的是一個連接配接開一個線程嗎。其實可以用線程池來解決這個問題,那說到線程池,那又如何解決線程與連接配接對應的問題,畢竟一個連接配接肯定是始終要在一個線程中處理消息的,否則跨線程處理消息的拼接将會是一個很大的麻煩。還有就是這種直接的方式不好設計接口給使用者使用,說直白點就是如何優雅的處理使用者資料讀和寫的問題。而muduo以及大部分的網絡庫都使用了reactor模式,可以很好的處理這兩個問題。

這幾天詳細讀了muduo的網絡處理部分,發現多線程處理是整個架構的精華。muduo是基于one loop per thread模型的。那麼什麼是one loop per thread模型呢?

字面意思上講就是每個線程裡有個loop,即消息循環。我們知道伺服器必定有一個監聽的socket和1到N個連接配接的socket,每個socket也必定有網絡事件。我們可以啟動設定數量的線程,讓這些線程來承擔網絡事件。

每個程序預設都會啟動一個線程,即這個線程不需要我們手動去建立,稱之為主線程。一般地我們讓主線程來承擔監聽socket的網絡事件,然後等待新的連接配接。至于新連接配接的socket的事件要不要在主線程中處理,這個得看我們啟動其他線程即工作線程的數量。如果啟動了工作線程,那麼新連接配接的socket的網絡事件一定是在工作線程中處理的。

每個線程的事件處理都是在一個EventLoop的while循環中,而每一個EventLoop都有一個多路事件複用解析器epoller。循環的主體部分是等待epoll事件觸發,進而處理事件。主線程EventLoop的epoller會添加監聽socket可讀事件,而工作線程一開始什麼都沒有添加(不過每個EventLoop會有一個wakeupChannel_,他會添加可讀事件,原因下面會講到),因為還沒有連接配接的産生。在沒有事件觸發之前,epoller都是阻塞的,導緻線程被挂起。

當有連接配接來到時,挂起的主線程恢複,會執行新連接配接的回調函數。在該函數中,會從線程池中取得一個線程來接管新連接配接socket的處理。前面提到,工作線程沒有添加任何事件,那将導緻工作線程一直被挂起。那麼問題來了,那他是如何處理新連接配接socket相關事件的呢,也就是說挂起的工作線程什麼時候恢複的呢?

上面提到,每個EventLoop還有一個wakeupChannel_,他會添加可讀事件。主線程通知工作線程去處理事件的時候,工作線程發現不在本線程的時間片中,于是往wakeupChannel_寫入一個int64大小位元組的資料,來喚醒沉睡的poller,這樣就激發工作線程了。

可以想象,每個線程中有一個泵,泵的作用就是提取消息并且分發給消息的業主調用,一個泵就是一個EventLoop對象。主線程隻管socket的監聽事件,并産生新的連接配接。新的連接配接也會有消息的産生,故需要将新的連接配接添加到一個消息泵中,這個消息泵可以是新的,也可以和别的連接配接複用,如下圖:

muduo 多線程的處理

有關代碼,下篇再分析。

繼續閱讀