muduo網路庫學習之EventLoop(二):程式(執行緒)wait/notify 和 EventLoop::runInLoop
1、程式(執行緒)wait/notify
pipe
socketpair
eventfd
socketpair
eventfd
eventfd 是一個比 pipe 更高效的執行緒間事件通知機制,一方面它比 pipe 少用一個 file descripor,節省了資源;另一方面,eventfd 的緩衝區管理也簡單得多,全部“buffer” 只有定長8 bytes,不像 pipe 那樣可能有不定長的真正 buffer。
C++ Code
1
2 3 4 5 6 7 8 9 |
// 該函式可以跨執行緒呼叫
void EventLoop::quit()
{ quit_ = true; if (!isInLoopThread()) { wakeup(); } } |
如果不是當前IO執行緒呼叫quit,則需要喚醒(wakeup())當前IO執行緒,因為它可能還阻塞在poll的位置(EventLoop::loop()),這樣再次迴圈判斷 while (!quit_) 才能退出迴圈。
一般情況下如果沒有呼叫quit(),poll沒有事件發生,也會超時返回(預設10s),但會繼續迴圈。
時序分析:
構造一個EventLoop物件,建構函式初始化列表,構造poller_, timeQueue_, wakeupFd_, wakeupChannel_ 等成員,在函式體中:
C++ Code
1
2 3 4 |
wakeupChannel_->setReadCallback(
boost::bind(&EventLoop::handleRead, this)); // we are always reading the wakeupfd wakeupChannel_->enableReading(); |
呼叫Channel::setReadCallback 註冊wakeupChannel_ 的回撥函式為EventLoop::handleRead, 呼叫Channel::enableReading(); 接著呼叫Channel::update(); 進而呼叫EventLoop::UpdataChannel(); 最後呼叫
Poller::updataChannel();(虛擬函式,具體由子類實現),此函式內新增一個新channel或者
更新此channel關注的事件,現在是將wakeupChannel_ 新增進PollPoller::channels_(假設Poller類用PollerPoller類實現) 中,並使用wakeupChannel_.fd_ 和 wakeupChannel_.events_ 構造一個struct pollfd, 並壓入pollfds_; 以後將關注wakeupChannel_
(wakeupFd_) 的可讀事件。
事件迴圈開始EventLoop::loop(),內部呼叫poll()(這裡假設呼叫的是PollPoller::poll(), 內部呼叫::poll())。::poll() 阻塞返回即事件發生,如timerfd_超時可讀; socket 有資料可讀/可寫; 非IO執行緒呼叫EventLoop:quit(), 進而呼叫wakeup(),非IO執行緒往wakeupFd_
中write 入8個位元組資料,此時wakeupFd_可讀。現在假設是wakeupFd_ 可讀,PollPoller::poll()呼叫PollPoller::fillActiveChannels()(虛擬函式), 函式內使用(struct pollfd).revents 設定此channel的revents_,然後將此channel 壓入EventLoop::activeChannels_ 中後返回。PollPoller::poll() 返回,EventLoop::loop()中遍歷activeChannels_,對每個活動channel呼叫Channel::handleEvent(),進而呼叫每個channel註冊的讀/寫回撥函式。
由上面分析可知,wakeupChannel_ 的回撥函式為EventLoop::handleRead,函式內呼叫read 掉 wakeupFd_ 的資料,避免一直觸發。
2、EventLoop::loop、runInLoop、queueInLoop、doPendingFunctors
EventLoop 有個pendingFunctors_ 成員:
C++ Code
1
2 |
typedef boost::function<void()> Functor;
std::vector<Functor> pendingFunctors_; |
四個函式的流程圖和實現如下:
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
// 事件迴圈,該函式不能跨執行緒呼叫
// 只能在建立該物件的執行緒中呼叫 void EventLoop::loop() {// 斷言當前處於建立該物件的執行緒中 assertInLoopThread(); while (!quit_) { pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); eventHandling_ = true; for (ChannelList::iterator it = activeChannels_.begin(); it != activeChannels_.end(); ++it) { currentActiveChannel_ = *it; currentActiveChannel_->handleEvent(pollReturnTime_); } currentActiveChannel_ = NULL; eventHandling_ = false; doPendingFunctors(); } } // 為了使IO執行緒在空閒時也能處理一些計算任務 // 在I/O執行緒中執行某個回撥函式,該函式可以跨執行緒呼叫 void EventLoop::runInLoop(const Functor& cb) { if (isInLoopThread()) { // 如果是當前IO執行緒呼叫runInLoop,則同步呼叫cb cb(); } else { // 如果是其它執行緒呼叫runInLoop,則非同步地將cb新增到佇列,讓IO執行緒處理 queueInLoop(cb); } } void EventLoop::queueInLoop(const Functor& cb) { { MutexLockGuard lock(mutex_); pendingFunctors_.push_back(cb); } // 呼叫queueInLoop的執行緒不是當前IO執行緒則需要喚醒當前IO執行緒,才能及時執行doPendingFunctors(); // 或者呼叫queueInLoop的執行緒是當前IO執行緒(比如在doPendingFunctors()中執行functors[i]() 時又呼叫了queueInLoop()) // 並且此時正在呼叫pending functor,需要喚醒當前IO執行緒
// 因為在此時doPendingFunctors() 過程中又新增了任務,故迴圈回去poll的時候需要被喚醒返回,進而繼續執行doPendingFunctors()
// 只有當前IO執行緒的事件回撥中呼叫queueInLoop才不需要喚醒 // 即在handleEvent()中呼叫queueInLoop 不需要喚醒,因為接下來馬上就會執行doPendingFunctors();
if (!isInLoopThread() || callingPendingFunctors_) { wakeup(); } } // 該函式只會被當前IO執行緒呼叫 void EventLoop::doPendingFunctors() { std::vector<Functor> functors; callingPendingFunctors_ = true; { MutexLockGuard lock(mutex_); functors.swap(pendingFunctors_); } for (size_t i = 0; i < functors.size(); ++i) { functors[i](); } callingPendingFunctors_ = false; } |
|
這樣,TimeQueue的兩個公有成員函式都可以跨執行緒呼叫,因為即使是被非IO執行緒呼叫,也會放進Queue,然後讓當前IO執行緒來執行:
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// 該函式可以跨執行緒呼叫 TimerId TimerQueue::addTimer(const TimerCallback &cb, Timestamp when, double interval) { Timer *timer = new Timer(cb, when, interval); loop_->runInLoop( // addTimeInLoop 只能在當前IO執行緒呼叫 boost::bind(&TimerQueue::addTimerInLoop, this, timer)); return TimerId(timer, timer->sequence()); } // 該函式可以跨執行緒呼叫 void TimerQueue::cancel(TimerId timerId) { loop_->runInLoop( // cancelInLoop 只能在當前IO執行緒呼叫 boost::bind(&TimerQueue::cancelInLoop, this, timerId)); } |
進而EventLoop類中的定時器操作函式 runAt, runAfter, runEvery, cancel 都可以跨執行緒呼叫,因為實現中呼叫了TimerQueue::addTimer 和 TimeQueue::cancel .
關於doPendingFunctors 的補充說明:
1、不是簡單地在臨界區內依次呼叫Functor,而是把回撥列表swap到functors中,這樣一方面減小了臨界區的長度(意味著不會阻塞其它執行緒的queueInLoop()),另一方面,也避免了死鎖(因為Functor可能再次呼叫queueInLoop())
2、由於doPendingFunctors()呼叫的Functor可能再次呼叫queueInLoop(cb),這時,queueInLoop()就必須wakeup(),否則新增的cb可能就不能及時呼叫了
3、muduo沒有反覆執行doPendingFunctors()直到pendingFunctors_為空而是每次poll 返回就執行一次,這是有意的,否則IO執行緒可能陷入死迴圈,無法處理IO事件。
測試程式碼:
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#include <muduo/net/EventLoop.h>
//#include <muduo/net/EventLoopThread.h> //#include <muduo/base/Thread.h> #include <stdio.h> using namespace muduo; using namespace muduo::net; EventLoop *g_loop; int g_flag = 0; void run4() { printf("run4(): pid = %d, flag = %d\n", getpid(), g_flag); g_loop->quit(); } void run3() { printf("run3(): pid = %d, flag = %d\n", getpid(), g_flag); g_loop->runAfter(3, run4); g_flag = 3; } void run2() { printf("run2(): pid = %d, flag = %d\n", getpid(), g_flag); g_loop->queueInLoop(run3); } void run1() { g_flag = 1; printf("run1(): pid = %d, flag = %d\n", getpid(), g_flag); g_loop->runInLoop(run2); g_flag = 2; } int main() { printf("main(): pid = %d, flag = %d\n", getpid(), g_flag); EventLoop loop; g_loop = &loop; loop.runAfter(2, run1); loop.loop(); printf("main(): pid = %d, flag = %d\n", getpid(), g_flag); } |
執行結果如下:
simba@ubuntu:~/Documents/build/debug/bin$ ./reactor_test05
20131108 02:17:05.204800Z 2319 TRACE IgnoreSigPipe Ignore SIGPIPE - EventLoop.cc:51
main(): pid = 2319, flag = 0
20131108 02:17:05.207647Z 2319 TRACE updateChannel fd = 4 events = 3 - EPollPoller.cc:104
20131108 02:17:05.208332Z 2319 TRACE EventLoop EventLoop created 0xBF9382D4 in thread 2319 - EventLoop.cc:76
20131108 02:17:05.208746Z 2319 TRACE updateChannel fd = 5 events = 3 - EPollPoller.cc:104
20131108 02:17:05.209198Z 2319 TRACE loop EventLoop 0xBF9382D4 start looping - EventLoop.cc:108
20131108 02:17:07.209614Z 2319 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 02:17:07.218039Z 2319 TRACE printActiveChannels {4: IN } - EventLoop.cc:271
20131108 02:17:07.218162Z 2319 TRACE readTimerfd TimerQueue::handleRead() 1 at 1383877027.218074 - TimerQueue.cc:62
run1(): pid = 2319, flag = 1
run2(): pid = 2319, flag = 1
run3(): pid = 2319, flag = 2
20131108 02:17:10.218763Z 2319 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 02:17:10.218841Z 2319 TRACE printActiveChannels {4: IN } - EventLoop.cc:271
20131108 02:17:10.218860Z 2319 TRACE readTimerfd TimerQueue::handleRead() 1 at 1383877030.218854 - TimerQueue.cc:62
run4(): pid = 2319, flag = 3
20131108 02:17:10.218885Z 2319 TRACE loop EventLoop 0xBF9382D4 stop looping - EventLoop.cc:133
main(): pid = 2319, flag = 3
simba@ubuntu:~/Documents/build/debug/bin$
20131108 02:17:05.204800Z 2319 TRACE IgnoreSigPipe Ignore SIGPIPE - EventLoop.cc:51
main(): pid = 2319, flag = 0
20131108 02:17:05.207647Z 2319 TRACE updateChannel fd = 4 events = 3 - EPollPoller.cc:104
20131108 02:17:05.208332Z 2319 TRACE EventLoop EventLoop created 0xBF9382D4 in thread 2319 - EventLoop.cc:76
20131108 02:17:05.208746Z 2319 TRACE updateChannel fd = 5 events = 3 - EPollPoller.cc:104
20131108 02:17:05.209198Z 2319 TRACE loop EventLoop 0xBF9382D4 start looping - EventLoop.cc:108
20131108 02:17:07.209614Z 2319 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 02:17:07.218039Z 2319 TRACE printActiveChannels {4: IN } - EventLoop.cc:271
20131108 02:17:07.218162Z 2319 TRACE readTimerfd TimerQueue::handleRead() 1 at 1383877027.218074 - TimerQueue.cc:62
run1(): pid = 2319, flag = 1
run2(): pid = 2319, flag = 1
run3(): pid = 2319, flag = 2
20131108 02:17:10.218763Z 2319 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 02:17:10.218841Z 2319 TRACE printActiveChannels {4: IN } - EventLoop.cc:271
20131108 02:17:10.218860Z 2319 TRACE readTimerfd TimerQueue::handleRead() 1 at 1383877030.218854 - TimerQueue.cc:62
run4(): pid = 2319, flag = 3
20131108 02:17:10.218885Z 2319 TRACE loop EventLoop 0xBF9382D4 stop looping - EventLoop.cc:133
main(): pid = 2319, flag = 3
simba@ubuntu:~/Documents/build/debug/bin$
g_loop->runInLoop(run2); 由於是在當前IO執行緒,故馬上執行run2(),此時flag 還是為1;在run2()內g_loop->queueInLoop(run3);
即把run3()新增到佇列,run2()返回,繼續g_flag=2,此時loop內已經處理完事件,執行doPendingFunctors(),就執行了run3(),
run3()內設定另一個3s定時器,run3()執行完回到loop繼續poll, 3s後超時執行run4(),此時flag=3。
參考:
《UNP》
muduo manual.pdf
《linux 多執行緒伺服器程式設計:使用muduo c++網路庫》
相關文章
- Netty 框架學習 —— EventLoop 和執行緒模型Netty框架OOP執行緒模型
- Netty中的執行緒處理EventLoopNetty執行緒OOP
- Java多執行緒學習(四)等待/通知(wait/notify)機制Java執行緒AI
- Java多執行緒 -- wait() 和 notify() 使用入門Java執行緒AI
- 執行緒安全(三個條件)Synchronzied,wait和notify執行緒AI
- Java多執行緒中wait 和 notify 方法理解Java執行緒AI
- 執行緒篇2:[- sleep、wait、notify、join、yield -]執行緒AI
- java多執行緒 wait() notify()簡單使用Java執行緒AI
- 瀏覽器eventLoop和node eventLoop瀏覽器OOP
- 多執行緒(一)、基礎概念及notify()和wait()的使用執行緒AI
- Python學習之程式和執行緒Python執行緒
- 【Java】【多執行緒】兩個執行緒間的通訊、wait、notify、notifyAllJava執行緒AI
- Java多執行緒中的wait/notify通訊模式Java執行緒AI模式
- 併發程式設計之Wait和Notify程式設計AI
- 執行緒間的同步與通訊(2)——wait, notify, notifyAll執行緒AI
- 瀏覽器EventLoop執行過程解析瀏覽器OOP
- EventLoopOOP
- 併發程式設計——執行緒中sleep(),yield(),join(),wait(),notify(),notifyAll()區別程式設計執行緒AI
- Netty原始碼死磕一(netty執行緒模型及EventLoop機制)Netty原始碼執行緒模型OOP
- java多執行緒基礎篇(wait、notify、join、sleep、yeild方法)Java執行緒AI
- Java 非同步程式設計之:notify 和 wait 用法Java非同步程式設計AI
- 構建一個基於事件分發驅動的EventLoop執行緒模型事件OOP執行緒模型
- 多執行緒學習(二)執行緒
- muduo網路庫Timestamp類
- I/O模型、Libuv和Eventloop模型OOP
- python 多程式和多執行緒學習Python執行緒
- EventLoop 的理解OOP
- eventLoop淺論OOP
- EventLoop二三事OOP
- 事件迴圈 EventLoop(Promise,setTimeOut,async/await執行順序)事件OOPPromiseAI
- 併發程式設計之 wait notify 方法剖析程式設計AI
- 二. 執行緒管理之執行緒池執行緒
- 深入理解Javascript之Callstack&EventLoopJavaScriptOOP
- JS基礎總結(5)—— JS執行機制與EventLoopJSOOP
- [JavaScript]js EventLoop And AsyncJavaScriptJSOOP
- Eventloop的祕密OOP
- nodejs中的eventLoopNodeJSOOP
- 好程式設計師大資料學習路線分享執行緒學習筆記二程式設計師大資料執行緒筆記
- netty系列之:EventLoop,EventLoopGroup和netty的預設實現NettyOOP