操作系統(tǒng)知識點總結--eventfd實現(xiàn)線程事件通知機制

什么是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。

  1. EFD_CLOEXEC:表示返回的eventfd文件描述符在fork后exec其他程序時會自動關閉這個文件描述符;
  2. EFD_NONBLOCK:設置返回的eventfd非阻塞;
  3. 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);
}
  1. EventLoop大多數時間都在運行l(wèi)oop中的循環(huán),也就是說想要在本線程內執(zhí)行runInLoop只有一種可能,那就是在調用handleEvents的時候,對應的文件描述符調用了runInLoop,此時處于LoopThread內,于是直接執(zhí)行cb。
  2. 再來看由其他線程調用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及對應喚醒

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。