muduo網路庫學習筆記(11):有用的runInLoop()函式
runInLoop()函式的有用之處
“EventLoop有一個非常有用的功能:在它的IO執行緒內執行某個使用者任務回撥,即EventLoop::runInLoop(const Functor& cb),其中Functor是boost::function<void()>。如果使用者在當前IO執行緒呼叫這個函式,回撥會同步進行;如果使用者在其他執行緒呼叫runInLoop(),cb會被加入佇列,IO執行緒會被喚醒來呼叫這個Functor。”
即我們可以線上程間方便地進行任務調配,而且可以在不用鎖的情況下保證執行緒安全。
下面通過對程式碼的分析來一探究竟。
原始碼分析
(1)開門見山,我們先來看runInLoop()函式
流程圖:
程式碼片段1:EventLoop::runInLoop()
檔名:EventLoop.cc
// 在IO執行緒中執行某個回撥函式,該函式可以跨執行緒呼叫
void EventLoop::runInLoop(const Functor& cb)
{
if (isInLoopThread())
{
// 如果是當前IO執行緒呼叫runInLoop,則同步呼叫cb
cb();
}
else
{
// 如果是其它執行緒呼叫runInLoop,則非同步地將cb新增到佇列
queueInLoop(cb);
}
}
函式的邏輯很簡單:判斷是否處於當前IO執行緒,是則執行這個函式,如果不是則將函式加入佇列。
(2)queueInLoop()函式
流程圖:
程式碼片段2:EventLoop::queueInLoop()
檔名:EventLoop.cc
void EventLoop::queueInLoop(const Functor& cb)
{
// 把任務加入到佇列可能同時被多個執行緒呼叫,需要加鎖
{
MutexLockGuard lock(mutex_);
pendingFunctors_.push_back(cb);
}
// 將cb放入佇列後,我們還需要在必要的時候喚醒IO執行緒來處理
// 必要的時候有兩種情況:
// 1.如果呼叫queueInLoop()的不是IO執行緒,需要喚醒
// 2.如果在IO執行緒呼叫queueInLoop(),且此時正在呼叫pending functor,需要喚醒
// 即只有在IO執行緒的事件回撥中呼叫queueInLoop()才無需喚醒
if (!isInLoopThread() || callingPendingFunctors_)
{
wakeup();
}
}
喚醒的時間點是怎麼選擇的呢?我們來回顧一下事件迴圈EventLoop::loop()中的一段程式碼:
程式碼片段3:EventLoop::loop()部分
檔名:EventLoop.cc
while (!quit_)
{
activeChannels_.clear();
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
for (ChannelList::iterator it = activeChannels_.begin();
it != activeChannels_.end(); ++it)
{
currentActiveChannel_ = *it;
currentActiveChannel_->handleEvent(pollReturnTime_);
}
// 執行pending Functors_中的任務回撥
// 這種設計使得IO執行緒也能執行一些計算任務,避免了IO執行緒在不忙時長期阻塞在IO multiplexing呼叫中
doPendingFunctors();
}
I.第一種情況易理解:呼叫queueInLoop的執行緒不是當前IO執行緒時,則需要喚醒當前IO執行緒,才能及時執行doPendingFunctors()。
II.第二種情況,呼叫queueInLoop()的執行緒是當前IO執行緒,比如在doPendingFunctors()中執行functors[i]() 時又呼叫了queueInLoop()。此時doPendingFunctors() 執行functors[i]() 過程中又新增了任務,故迴圈回去到poll的時候需要被喚醒返回,進而繼續執行doPendingFunctors() 。
只有在當前IO執行緒的事件回撥中呼叫queueInLoop才不需要喚醒,即在handleEvent()中呼叫queueInLoop ()不需要喚醒,因為接下來馬上就會執行doPendingFunctors()。
(3)doPendingFunctors()函式
EventLoop::doPendingFunctors()不是簡單地在臨界區依次呼叫Functor,而是把回撥列表swap()到區域性變數functors中,這樣做,一方面減小了臨界區的長度(不會阻塞其他執行緒呼叫queueInLoop()),另一方面避免了死鎖(因為Functor可能再呼叫queueInLoop())。
程式碼片段4:EventLoop::doPendingFunctors()
檔名:EventLoop.cc
void EventLoop::doPendingFunctors()
{
std::vector<Functor> functors;
callingPendingFunctors_ = true;
// 把回撥列表swap()到區域性變數functors中
{
MutexLockGuard lock(mutex_);
functors.swap(pendingFunctors_);
}
// 依次執行回撥列表中的函式
for (size_t i = 0; i < functors.size(); ++i)
{
functors[i]();
}
callingPendingFunctors_ = false;
}
muduo這裡沒有反覆執行doPendingFunctors()直到pendingFunctors_為空,反覆執行可能會使IO執行緒陷入死迴圈,無法處理IO事件。
(4)我們回頭再來看一下–怎樣實現喚醒
傳統的程式/執行緒間喚醒辦法是用pipe或者socketpair,IO執行緒始終監視管道上的可讀事件,在需要喚醒的時候,其他執行緒向管道中寫一個位元組,這樣IO執行緒就從IO multiplexing阻塞呼叫中返回。pipe和socketpair都需要一對檔案描述符,且pipe只能單向通訊,socketpair可以雙向通訊。
下面介紹一下muduo所採用的一種高效的程式/執行緒間事件通知機制–eventfd。
// 標頭檔案
#include <sys/eventfd.h>
// 為事件通知建立檔案描述符
// 引數initval表示初始化計數器值
// 引數flags可取EFD_NONBLOCK、EFD_CLOEXEC、EFD_SEMAPHORE
int eventfd(unsigned int initval, int flags);
它的高效體現在:一方面它比 pipe 少用一個 fd,節省了資源;另一方面,eventfd 的緩衝區管理也簡單得多,全部buffer只有定長8 bytes,不像 pipe 那樣可能有不定長的真正 buffer。
程式碼片段5:EventLoop::wakeup()
檔名:EventLoop.cc
void EventLoop::wakeup()
{
uint64_t one = 1;
// 向wakupFd_中寫入8位元組從而喚醒,wakeupFd_即eventfd()所建立的檔案描述符
ssize_t n = ::write(wakeupFd_, &one, sizeof one);
if (n != sizeof one)
{
LOG_ERROR << "EventLoop::wakeup() writes " << n << " bytes instead of 8";
}
}
相關文章
- muduo網路庫學習筆記(1):Timestamp類筆記
- muduo網路庫學習筆記(2):原子性操作筆記
- muduo網路庫學習筆記(3):Thread類筆記thread
- muduo網路庫學習筆記(14):chargen服務示例筆記
- muduo網路庫學習之EventLoop(二):程式(執行緒)wait/notify 和 EventLoop::runInLoopOOP執行緒AI
- muduo網路庫學習筆記(13):TcpConnection生命期的管理筆記TCP
- muduo網路庫學習筆記(10):定時器的實現筆記定時器
- muduo網路庫學習筆記(9):Reactor模式的關鍵結構筆記React模式
- muduo網路庫學習筆記(8):高效日誌類的封裝筆記封裝
- muduo網路庫學習筆記(5):執行緒池的實現筆記執行緒
- muduo網路庫學習筆記(12):TcpServer和TcpConnection類筆記TCPServer
- muduo網路庫學習筆記(7):執行緒特定資料筆記執行緒
- muduo網路庫學習筆記(6):單例類(執行緒安全的)筆記單例執行緒
- muduo網路庫學習筆記(4):互斥量和條件變數筆記變數
- muduo網路庫學習之muduo_http 庫涉及到的類HTTP
- muduo網路庫學習之muduo_inspect 庫涉及到的類
- muduo網路庫學習筆記(15):關於使用stdio和iostream的討論筆記iOS
- muduo網路庫學習之EventLoop(七):TcpClient、ConnectorOOPTCPclient
- 深度學習——loss函式的學習筆記深度學習函式筆記
- async函式學習筆記。函式筆記
- Go 函式 學習筆記Go函式筆記
- 分析函式(學習筆記)函式筆記
- 生成函式 學習筆記函式筆記
- TS學習筆記(四):函式筆記函式
- JavaScript學習筆記 - 原生函式JavaScript筆記函式
- Golang學習筆記-1.6 函式Golang筆記函式
- Oracle學習筆記(6)——函式Oracle筆記函式
- LoadRunner函式學習筆記函式筆記
- MYSQL學習筆記14: 函式MySql筆記函式
- 學習筆記:javascript中的Generator函式筆記JavaScript函式
- 【學習筆記】網路流筆記
- [網路]NIO學習筆記筆記
- 網路流學習筆記筆記
- python學習筆記(六)——函式Python筆記函式
- OpenCV學習筆記(4)——mixChannels函式OpenCV筆記函式
- OpenCV學習筆記(5)——normalize函式OpenCV筆記ORM函式
- Flutter學習筆記(4)--Dart函式Flutter筆記Dart函式
- js純函式學習筆記(一)JS函式筆記