什么是eventfd
eventfd是Linux 2.6提供的一種系統(tǒng)調用,它可以用來實現(xiàn)事件通知。eventfd包含一個由內核維護的64位無符號整型計數器,創(chuàng)建eventfd時會返回一個文件描述符,進程/線程可以通過對這個文件描述符進行read/write來讀取/改變計數器的值,從而實現(xiàn)進程/線程間通信。
eventfd
eventfd - 事件通知文件描述符
eventfd系統(tǒng)函數 - 創(chuàng)建一個能被用戶應用程序用于時間等待喚醒機制的eventfd對象。
eventfd 單純的使用文件描述符實現(xiàn)的線程間的通知機制,可以很好的融入select、poll、epoll的I/O復用機制中。
創(chuàng)建eventfd
#include <sys/eventfd.h>
int eventfd(unsigned int initval ,int flags );
參數意義:
initval:
創(chuàng)建eventfd時它所對應的64位計數器的初始值;
flags:
eventfd文件描述符的標志,可由三種選項組成:EFD_CLOEXEC、EFD_NONBLOCK和EFD_SEMAPHORE。
- EFD_CLOEXEC:表示返回的eventfd文件描述符在fork后exec其他程序時會自動關閉這個文件描述符;
- EFD_NONBLOCK:設置返回的eventfd非阻塞;
- EFD_SEMAPHORE: 表示將eventfd作為一個信號量來使用。
讀eventfd read(2)
1.read函數會從eventfd對應的64位計數器中讀取一個8字節(jié)的整型變量;
2.read函數設置的接收buf的大小不能低于8個字節(jié),否則read函數會出錯,errno為EINVAL;
3.read函數返回的值是按小端字節(jié)序的;
4.如果eventfd設置了EFD_SEMAPHORE,那么每次read就會返回1,并且讓eventfd對應的計數器減一;如果eventfd沒有設置EFD_SEMAPHORE,那么每次read就會直接返回計數器中的數值,read之后計數器就會置0。不管是哪一種,當計數器為0時,如果繼續(xù)read,那么read就會阻塞(如果eventfd沒有設置EFD_NONBLOCK)或者返回EAGAIN錯誤(如果eventfd設置了EFD_NONBLOCK)。
寫eventfd write(2)
1.在沒有設置EFD_SEMAPHORE的情況下,write函數會將發(fā)送buf中的數據寫入到eventfd對應的計數器中,最大只能寫入0xffffffffffffffff,否則返回EINVAL錯誤;
2.在設置了EFD_SEMAPHORE的情況下,write函數相當于是向計數器中進行“添加”,比如說計數器中的值原本是2,如果write了一個3,那么計數器中的值就變成了5。如果某一次write后,計數器中的值超過了0xfffffffffffffffe(64位最大值-1),那么write就會阻塞直到另一個進程/線程從eventfd中進行了read(如果write沒有設置EFD_NONBLOCK),或者返回EAGAIN錯誤(如果write設置了EFD_NONBLOCK)。
除此之外,eventfd還支持select和poll,與一般的讀寫描述符相類似。
EventLoop對eventfd的封裝
所增加的接口及成員:
typedef std::function<void()> Functor;
//如果用戶在當前IO線程調用這個函數, 回調會同步進行; 如果用戶在其他線程調用runInLoop(),cb會被加入隊列, IO線程會被喚醒來調用這個Functor。
void runInLoop(const Functor& cb);
//寫已注冊到poll的eventfd 通知poll 處理讀事件。
void wakeup(); //是寫m_wakeupFd 通知poll 處理讀事件。
//會將回調添加到容器,同時通過wakeup()喚醒poll()調用容器內的回調。
void queueInLoop(const Functor& cb);
private:
//used to waked up
//poll回調讀事件,處理eventfd
void handleRead();
//處理掛起的事件
void doPendingFunctors();
int m_wakeupFd;
std::unique_ptr<Channel> p_wakeupChannel;
mutable MutexLock m_mutex;
bool m_callingPendingFunctors; /* atomic */
std::vector<Functor> m_pendingFunctors; // @GuardedBy mutex_
工作時序:
(runInLoop() -> quueInLoop())/queueInLoop() -> wakeup() -> poll() -> handleRead() -> doPendingFunctors()
runInLoop()
void EventLoop::runInLoop(const Functor& cb)
{
if(isInloopThread())
cb();
else
queueInLoop(cb);
}
- EventLoop大多數時間都在運行l(wèi)oop中的循環(huán),也就是說想要在本線程內執(zhí)行runInLoop只有一種可能,那就是在調用handleEvents的時候,對應的文件描述符調用了runInLoop,此時處于LoopThread內,于是直接執(zhí)行cb。
- 再來看由其他線程調用runInLoop的情況,首先會直接進入queueInLoop分支,把cb加入到待處理的回調函數數組里。因為我們要讓IO線程及時處理我們的cb,我們勢必要wakeupIO線程,讓他從poll中出來,所以我們執(zhí)行wakeup。
至于另一種情況,我們先來假設在IO線程中也會調用queueInLoop,那么這種調用會在兩個地方出現(xiàn),第一是handleEvent,我們此時無需wakeup,因為IO線程處理完所有的channel之后便會執(zhí)行doPendingFunctors,處理我們加入的cb。第二種情況是在執(zhí)行doPendingFunctors的時候,執(zhí)行functorsi時執(zhí)行到了queueInLoop,那我們?yōu)榱俗孖O線程在執(zhí)行完所有的functors后立即處理我們這個時候加入的cb,還要喚醒他,也就是說提前喚醒,等到下次IO運行到poll直接就返回了,如果不這樣我們的cb可能會被晚處理一段時間。
void EventLoop::runInLoop(Functor&&cb)
{
if(isInLoopThread())
cb();
else
queueInLoop(std::move(cb));
}
void EventLoop::queueInLoop(Functor&&cb)
{
{
MutexLockGuard lock(mutex_);
pendingFunctors_.emplace_back(std::move(cb));
}
if(!isInLoopThread()||callingPendingFunctors_)
wakeup();
}
void EventLoop::loop()
{
assert(!looping_);
assert(isInLoopThread());
looping_=true;
quit_=false;
std::vector<SP_Channel>ret;
while(!quit_)
{
ret.clear();
ret=poller_->poll();
eventHandlding_=true;
for(auto & it :ret)
it->handleEvents();
eventHandling_=false;
doPendingFunctors();
poller_->handleExpired();
}
looping_=false;
}
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;
}
參考鏈接:
Linux進程間通信——eventfd
eventfd實現(xiàn)線程事件通知機制
關于muduo庫中EventLoop的runInLoop功能
muduo庫發(fā)送數據簡述
muduo網絡庫學習(四)事件驅動循環(huán)EventLoop
muduo net庫學習筆記4——事件驅動循環(huán)EventLoop、runInLoop和queueInLoop及對應喚醒