開始
epoll 是 Linux 提供的 I/O event notification facility。在需要監(jiān)聽的 fd 數(shù)量很多(成千上萬)而同一時刻可讀/可寫的數(shù)量又比較少(幾個韭畸?幾十個喻犁?幾百個?)的情況下上岗,性能要明顯優(yōu)于 select福荸、poll。
API
與 epoll 直接相關(guān)的 API 有:
創(chuàng)建 epoll fd:epoll_create/epoll_create1
操作 epoll fd:epoll_ctl
監(jiān)聽 epoll fd:epoll_wait/epoll_pwait
創(chuàng)建
int epoll_create(int size);
int epoll_create1(int flags);
- epoll_create 用于創(chuàng)建一個 epoll fd肴掷。從 Linux2.6.8 開始敬锐,size 參數(shù)就被廢棄了,但是使用時傳入的參數(shù)必須大于0呆瞻。
- epoll_create1 的參數(shù) flags 可以為 0 或 EPOLL_CLOEXEC台夺。
- flags 為 0:epoll_create1 的作用和 epoll_create 一樣。
- flags 為 EPOLL_CLOEXEC:將返回的 epoll fd 設(shè)置為 close on exec痴脾。
操作
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
- 在 epfd 上設(shè)置 (op參數(shù)) 描述符 fd 的事件 (event 參數(shù))颤介。
- epfd 是 epoll_create 的返回值。
- fd 是想要操作的文件描述符赞赖。
- op 是操作的類型滚朵,其取值有:
- EPOLL_CTL_ADD
- EPOLL_CTL_MOD
- EPOLL_CTL_DEL
- event 是 fd 所關(guān)心的事件。對于 EPOLL_CTL_DEL前域,可以傳 NULL(BUG:Linux2.6.9 之前不能傳NULL)辕近。
typedef union epoll_data {
void* ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; // events is a bit set
epoll_data_t data;
};
- epoll_event 的成員 events 是一個 bit set。其值可以是下面各值的或匿垄。
- EPOLLINT:監(jiān)聽可讀事件移宅。
- EPOLLOUT:監(jiān)聽可寫事件。
- EPOLLRDHUP :監(jiān)聽對端斷開連接椿疗。
- EPOLLPRI:外帶數(shù)據(jù)漏峰。
- EPOLLERR:Error condition happened on the associated file descriptor.
- EPOLLHUP:Hang up happened on the associated file descriptor.
- EPOLLET:邊沿觸發(fā),epoll 監(jiān)聽的 fd 默認是電平觸發(fā)变丧。
- EPOLLONESHOT:對應(yīng) fd 的事件被觸發(fā)通知后芽狗,需要重新調(diào)用 epoll_ctl 對其進行修改(EPOLL_CTL_MOD),才能恢復(fù)事件觸發(fā)通知痒蓬。
監(jiān)聽
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event* events, int maxevents, int timeout, const sigset_t* sigmask);
監(jiān)聽的接口比較簡單童擎,暫時沒什么好介紹。
線程安全性
在知乎上看到一個問題討論 epoll 的線程安全性攻晒,里面提到 github 上的一個 issue顾复,有一段話:
One thread is calling
epoll_wait()
and blocks there. After that another thread is adding a new file descriptor to the epoll file. Becauseepoll_wait()
will not take this change into consideration, it will not be woken up on that file descriptor's events.
簡單說就是,一個線程阻塞在 epoll_wait
上鲁捏,另一個線程調(diào)用 epoll_ctl
設(shè)置 fd 會有問題芯砸。
但是,我在 man epoll_wait
中看到的內(nèi)容,卻說這樣做是安全的:
While one thread is blocked in a call to epoll_pwait(), it is possible for another thread to add a file descriptor to the waited-upon epoll instance. If the new file descriptor becomes ready, it will cause the epoll_wait() call to unblock.
自己簡單寫一個測試工具:一個線程負責(zé) accept
TCP 連接假丧,然后調(diào)用 epoll_ctl
將新連接注冊到 epoll双揪。另一個線程負責(zé) epoll_wait
,讀取 client 發(fā)送過來的數(shù)據(jù)包帚,并簡單打印出來渔期。并沒有出現(xiàn),這個 github issue 提到的問題…
網(wǎng)上看到渴邦,一個線程負責(zé) accept
疯趟,其它線程負責(zé) epoll_wait
的主流方案是,通過 pipe
或 eventfd
傳送 fd谋梭,在負責(zé) epoll_wait
的線程中執(zhí)行注冊信峻。(同時可以通過對多個 epoll_wait
的線程進行負載均衡。)
小結(jié)
最近感覺近幾年 Linux 內(nèi)核更新得挺快的瓮床,需要的時候還是去看對應(yīng)版本的 man page 比較靠譜…
另外盹舞,學(xué)習(xí)學(xué)習(xí)就好,I/O 多路復(fù)用的事還是交給庫和框架吧 ^_^
參考文檔
- The method to epoll’s madness
- EPOLL_CTL_DISABLE and multithreaded applications
- libevent實現(xiàn)多線程隘庄,one loop per thread矾策,多線程通信
- epoll的線程切換的問題?
- linux的epoll_wait以及epoll_ctl是否線程安全?
- 為什么epoll是線程安全峭沦?
- one loop per thread 的服務(wù)器程序贾虽,是一個線程監(jiān)聽一個端口嗎?
- epoll_wait() is oblivious to concurrent updates via epoll_ctl()
(2018.07.29 更新“線程安全性”一節(jié)吼鱼。)