一扇門鈴,萬向感應——用 eventfd 實現零延遲通信

🔍 本篇概要

  • eventfdLinux 提供的一種輕量級事件通知機制。你可以把它想象成一個“計數器盒子”。
  • 它里面維護的是一個64位的計數器。
  • 寫入:往盒子里放一些數字(比如 1、5、10),表示有幾件事發生了。
  • 讀取:從盒子里取出數字,每取一次就減少一個(或者一次性全拿走)。

它非常適合用來做:

  • 多線程/進程之間的通信。
  • 異步通知(如 epoll 配合使用)。
  • 控制并發資源訪問(類似信號量)。

在這里插入圖片描述

一· 🛠 如何創建 eventfd

如下:

#include <sys/eventfd.h>
int efd = eventfd(initval, flags);

解釋:

參數名含義
initval初始化值(初始計數器數值)
flags標志位(可選,影響行為)
  • 這里對于initval是給它設置初始值,如果不write寫入,計數器就是這個初始值,否者就是write寫入的那個值。

二.🎯 eventfd 的所有標志詳解(Flags

下面是 eventfd() 支持的所有標志:

標志名含義是否推薦使用
EFD_SEMAPHORE以信號量方式工作(每次讀取減 1)? 推薦
EFD_CLOEXEC執行 exec 時自動關閉描述符? 推薦
EFD_NONBLOCK設置為非阻塞模式? 視需求而定
EFD_SHARED_FCNTL_FLAGS允許在 fork 后共享文件鎖(Linux 4.7+)?? 較少用
0(默認)默認行為(不帶任何標志)? 可用

詳解 (通俗解釋通俗易懂版本)

1?? EFD_SEMAPHORE —— 類似信號量

效果:

啟用這個標志后,每次 read() 會把計數器減去 1,并返回 1。

🧠 比如:

你有一個糖果罐子,里面有 5 顆糖:

  • 不加這個標志 → 第一個人來,把 5 顆都拿走了。
  • 加了這個標志 → 每個人只能拿 1 顆,共 5 個人能拿到。

下面我們代碼演示下:

#include <sys/eventfd.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <errno.h>int main() {int efd = eventfd(5, EFD_SEMAPHORE| EFD_NONBLOCK); // 啟用兩個標志uint64_t val;for (int i = 0; i < 6; ++i) {ssize_t s = read(efd, &val, sizeof(val));if (s == -1) {if (errno == EAGAIN|EWOULDBLOCK)printf("No more events.\n");elseperror("read");} else {printf("Read: %llu\n", (unsigned long long)val);}}close(efd);return 0;
}

效果如下:

在這里插入圖片描述

  • 這里我們往文件里寫了個5;也就可以理解成它的計數器被從一開始的0變成了5;而我們設置了semaphore模式,也就是每次計數器都會自動減一(一般如果設置了這個(在允許的條件下),此時每次read讀出來的都是1)。
  • 然后我們又設置了非阻塞模式(nonblock),也就是不會阻塞,因此看到了上面的效果。

2?? EFD_CLOEXEC —— 自動關閉(exec 時)

效果:

當你調用 exec()(運行新程序)時,這個 eventfd 描述符會自動關閉,防止被新程序繼承。

🧠 比如:

你在執行一個新的程序,不想讓這個程序看到你之前的“糖果罐子”,那就加上這個標志。

efd = eventfd(0, EFD_CLOEXEC); // exec 時自動關閉
  • 這里我們一般使用的時候默認加上就好。

3?? EFD_NONBLOCK —— 非阻塞讀寫

效果:

設置為非阻塞模式后:

  • 如果當前沒有數據可讀,read() 不會等待,而是立即返回錯誤碼 EAGAIN如果緩沖區滿了,write() 也不會等待,而是立即返回 EAGAIN

🧠 比如:

你去看糖果罐子,如果里面沒糖了,你不等,直接離開。

演示下:

#include <sys/eventfd.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <errno.h>int main() {int efd = eventfd(0, EFD_NONBLOCK);uint64_t val;
ssize_t s = read(efd, &val, sizeof(val));
if (s == -1 && errno == EAGAIN) {printf("現在沒有事件發生\n");
}return 0;
}

效果:

發現如果設置了:

在這里插入圖片描述

  • 直接返回-1,然后查看錯誤碼即可判斷是非阻塞模式。

如果沒設置:

在這里插入圖片描述

  • 發現一直阻塞住。

4?? EFD_SHARED_FCNTL_FLAGS(Linux 4.7+)

效果:

允許多個 fork 出來的子進程共享這個 eventfd 的文件鎖狀態(很少用)。

🧠 舉例理解:

多個小孩一起管理同一個糖果罐子,不會互相干擾。

?? 這個標志只在較新的 Linux 內核中支持,一般用戶不需要關心。

因此,這里就不演示,也不常用。

三.綜合測試體驗下

代碼如下:

#include <sys/eventfd.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <errno.h>int main() {int efd = eventfd(5, EFD_SEMAPHORE|EFD_CLOEXEC| EFD_NONBLOCK); // 啟用兩個標志uint64_t val;for (int i = 0; i < 6; ++i) {ssize_t s = read(efd, &val, sizeof(val));if (s == -1) {if (errno == EAGAIN|EWOULDBLOCK)printf("No more events.\n");elseperror("read");} else {printf("Read: %llu\n", (unsigned long long)val);}}close(efd);return 0;
}

效果:

在這里插入圖片描述

  • 這里我們設置了非阻塞,因此最后會看到 NO more events,其次就是計數器寫成5,每次讀取都減1,然后每次讀出的都是1,當最后一次減完0了,然后是非阻塞因此會這樣。

如果我們寫入的值和eventfd本身初始化的值不同呢(他就會按照寫入的來初始化計數器了):

代碼如下:

#include <sys/eventfd.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <errno.h>int main() {int efd = eventfd(5, EFD_SEMAPHORE|EFD_CLOEXEC| EFD_NONBLOCK); // 啟用兩個標志uint64_t val=10;ssize_t s = write(efd, &val, sizeof(val));for (int i = 0; i < 6; ++i) {ssize_t s = read(efd, &val, sizeof(val));if (s == -1) {if (errno == EAGAIN|EWOULDBLOCK)printf("No more events.\n");elseperror("read");} else {printf("Read: %llu\n", (unsigned long long)val);}}close(efd);return 0;
}
  • 因此當它讀完5個1后還會繼續讀,直到完成10個:

效果:

在這里插入圖片描述

如果不設置EFD_SEMAPHORE呢?

代碼如下:

#include <sys/eventfd.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
#include <errno.h>int main()
{int efd = eventfd(5, EFD_CLOEXEC | EFD_NONBLOCK); // 啟用兩個標志uint64_t val;for (int i = 0; i < 6; ++i){ssize_t s = read(efd, &val, sizeof(val));if (s == -1){if (errno == EAGAIN | EWOULDBLOCK)printf("No more events.\n");elseperror("read");}else{printf("Read: %llu\n", (unsigned long long)val);}}close(efd);return 0;
}
  • 此時它就會一次性都讀出來,然后計數器瞬間清零。

效果:

在這里插入圖片描述

四.相關問題及使用技巧

相關問題:

問題回答
eventfd 是不是只能用于線程間通信?不是,也可以用于父子進程之間通信
eventfd 能不能和 epoll 一起用?當然可以!這是最常見用法之一
eventfd 和 pipe 有什么區別?eventfd 更輕量,適合簡單通知;pipe 適合傳輸大量數據
eventfd 的最大值是多少?最大值是 0xFFFFFFFFFFFFFFFE(接近 18e18)
eventfd 會不會導致內存泄漏?不會,只要記得 close(efd) 就行

使用技巧:(個人看法)

一般我們可以默認把EFD_CLOEXEC加上;然后對于需求來決定是否加上EFD_NONBLOCK;對于EFD_SEMAPHORE也就是想讓一次讀完還是多次也是根據自己需求來完成的(常用的也就是這三個)。

五.簡單基于eventfdepoll多線程通知測試

大致測流程:

  • 初始化 eventfd。
  • 初始化 epoll。
  • 將 eventfd 注冊到 epoll。
  • 啟動多個線程調用 epoll_wait 等待事件。
  • 主線程寫入 eventfd 觸發事件。
  • 所有監聽線程收到事件并處理。

看圖:

在這里插入圖片描述

源碼:

#include <iostream>
#include <thread>
#include <vector>
#include <sys/eventfd.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <cstring>
#include <cstdint>
#include <functional>
#include <chrono>// 線程數量
const int THREAD_COUNT = 3;int main() {// 1. 創建 eventfd (初始值為0)int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);if (efd == -1) {perror("eventfd");return 1;}// 2. 創建 epoll 實例int epfd = epoll_create1(0);if (epfd == -1) {perror("epoll_create1");close(efd);return 1;}// 3. 將 eventfd 添加進 epollstruct epoll_event ev;ev.events = EPOLLIN;   // 只關心可讀事件ev.data.fd = efd;if (epoll_ctl(epfd, EPOLL_CTL_ADD, efd, &ev) == -1) {perror("epoll_ctl: add");close(epfd);close(efd);return 1;}// 4. 創建線程池,每個線程調用 epoll_wait 等待事件std::vector<std::thread> threads;for (int i = 0; i < THREAD_COUNT; ++i) {threads.emplace_back([=]() {std::cout << "Thread [" << std::this_thread::get_id() << "] is waiting for event..." << std::endl;struct epoll_event events[10];while (true) {int n = epoll_wait(epfd, events, 10, -1); // 永遠等待if (n == -1) {perror("epoll_wait error");break;}for (int j = 0; j < n; ++j) {if (events[j].data.fd == efd && (events[j].events & EPOLLIN)) {uint64_t u;ssize_t s = read(efd, &u, sizeof(uint64_t));if (s != sizeof(uint64_t)) {perror("read eventfd");continue;}std::cout << "Thread [" << std::this_thread::get_id()<< "] received event, count: " << u << std::endl;}}}});}// 5. 主線程休眠一段時間后發送事件std::this_thread::sleep_for(std::chrono::seconds(3));std::cout << "Main thread is sending event to all workers..." << std::endl;uint64_t u = 1;if (write(efd, &u, sizeof(uint64_t)) != sizeof(uint64_t)) {perror("write eventfd");}// 6. 等待所有線程結束(這里為了簡單,實際應優雅退出)std::this_thread::sleep_for(std::chrono::seconds(2)); // 給子線程足夠時間響應for (auto& t : threads) {if (t.joinable()) {t.detach(); // 或者 join()}}// 7. 清理資源close(epfd);close(efd);return 0;
}

先看現象:

在這里插入圖片描述

解釋下:

  • 首先搞三線程,然后往epoll模型的監測fd中加入efd,三個線程都進行監測;如果主線程往efd中寫入了1,那么就只會被一個線程讀取然后打印出來,其他線程都在epoll這里阻塞;最后全部線程都被終止即結束。

六. 小白總結

下面是博主總結的一張使用圖:

在這里插入圖片描述

通俗總結:

eventfd 就像一個“計數器盒子”,你可以往里放數字,也可以往外取。通過設置不同的標志(flag),你可以控制它是“一次全拿走”還是“每次拿一個”,還可以讓它“不阻塞”、“自動關閉”等等。掌握這些標志,就能靈活運用它來做多線程同步、異步通知等高級功能!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/88736.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/88736.shtml
英文地址,請注明出處:http://en.pswp.cn/web/88736.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于Node.js的線上教學系統的設計與實現(源碼+論文+調試+安裝+售后)

感興趣的可以先收藏起來&#xff0c;還有大家在畢設選題&#xff0c;項目以及論文編寫等相關問題都可以給我留言咨詢&#xff0c;我會一一回復&#xff0c;希望幫助更多的人。系統背景近年來&#xff0c;全球數字化浪潮的推進與教育公平化需求的增長&#xff0c;促使線上教學迎…

互斥鎖詳解(操作系統os)

1. 互斥鎖 (Mutex) - 檔案室的“智能鎖”首先&#xff0c;我們給之前討論的那些“鎖”一個正式的名字&#xff1a;互斥鎖 (Mutex)。概念&#xff1a;你可以把它簡單理解成檔案室門上的一把“智能鎖”。它只有兩種狀態&#xff1a;locked (已上鎖) 或 unlocked (未上鎖)。操作&a…

自動潤滑系統:從 “盲目養護“ 到智能精注的工業運維革命

?在工業運維的漫長歷史中&#xff0c;傳統潤滑模式如同"定時喂飯"——無論設備實際需求&#xff0c;僅憑經驗或固定周期執行潤滑作業。這種模式埋下兩大隱患&#xff1a;過度潤滑&#xff1a;某汽車生產線曾因季度性強制潤滑&#xff0c;每年浪費1.2噸潤滑脂&#x…

【Java八股文總結 — 包學會】(二)計算機網絡

1.一條url輸入到瀏覽器最后顯示頁面的過程 URL解析與處理 瀏覽器解析URL&#xff08;如https://www.example.com/page&#xff09; 分離協議&#xff08;https&#xff09;、域名&#xff08;www.example.com&#xff09;和資源路徑&#xff08;/page&#xff09; 檢查HSTS預加…

力扣61.旋轉鏈表

給你一個鏈表的頭節點 head &#xff0c;旋轉鏈表&#xff0c;將鏈表每個節點向右移動 k 個位置。示例 1&#xff1a;輸入&#xff1a;head [1,2,3,4,5], k 2 輸出&#xff1a;[4,5,1,2,3]示例 2&#xff1a;輸入&#xff1a;head [0,1,2], k 4 輸出&#xff1a;[2,0,1]提示…

深度剖析:std::vector 內存機制與 push_back 擴容策略

深度剖析&#xff1a;std::vector 內存機制與 push_back 擴容策略 1. std::vector 核心內部結構 #mermaid-svg-8HOj3MqsD6UVgEeA {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-8HOj3MqsD6UVgEeA .error-icon{fill:…

GROW領導力模型

GROW領導力模型是由英國教練格雷厄姆亞歷山大&#xff08;Graham Alexander&#xff09;、艾倫Fine和約翰惠特默&#xff08;John Whitmore&#xff09;在20世紀80年代提出的&#xff0c;最初用于體育教練領域&#xff0c;后來被廣泛應用于企業管理、領導力發展和個人成長中。它…

打破并發瓶頸:虛擬線程實現詳解與傳統線程模型的性能對比

目錄 一、定義與特性 二、虛擬線程實現 2.1 使用 Thread.startVirtualThread() 創建 2.2 使用 Thread.ofVirtual() 創建 2.3 使用 ThreadFactory 創建 2.4 使用 Executors.newVirtualThreadPerTaskExecutor()創建 三、虛擬線程和普通線程的區別 3.1 線程管理方式不同 3…

“28項評測23項SOTA——GLM-4.1V-9B-Thinking本地部署教程:10B級視覺語言模型的性能天花板!

一、模型介紹 GLM-4.1V-9B-Thinking是由智譜AI聯合清華大學團隊推出的多模態大模型&#xff0c;以GLM-4-9B-0414基座模型為底&#xff0c;通過引入“思維鏈推理機制”和“課程采樣強化學習策略”&#xff08;Reinforcement Learning with Curriculum Sampling&#xff09;&…

推薦系統-Random算法

Random算法總結引言 在推薦系統研究與應用中&#xff0c;我們常常需要一些簡單的基線算法來衡量更復雜算法的性能提升。Random&#xff08;隨機推薦&#xff09;算法是最基礎的基線方法之一&#xff0c;它通過隨機生成評分來模擬用戶對物品的偏好。雖然這種方法看似簡單&#x…

Django--02模型和管理站點

Django–02模型與站點管理 Part 2: Models and the admin site 本教程承接Django–01的內容。我們將設置數據庫、創建你的第一個模型&#xff0c;并快速了解 Django 自動生成的管理站點。 文章目錄Django--02模型與站點管理前言一、設置數據庫1.1 參考文檔鏈接1.2 默認設置1.3…

CS課程項目設計1:交互友好的井字棋游戲

最近突然想開設一個專欄了&#xff0c;專門為計算機專業的同行分享一些入門級的課程項目設計&#xff0c;旨在讓同學更好地了解CS項目的設計流程&#xff0c;同時給出代碼來介紹coding過程。 今天要分享的是第一個CS課程項目&#xff1a;交互友好的井字棋游戲。 1. 研究目的 井…

首個自動駕駛VLA綜述介紹

當視覺(Vision)、語言(Language)和行動(Action)三大能力在一個模型中融合,自動駕駛的未來將走向何方? 近日,來自麥吉爾大學、清華大學、小米公司和威斯康辛麥迪遜的研究團隊聯合發布了全球首篇針對自動駕駛領域的視覺-語言-行動(Vision-Language-Action, VLA)模型的…

C# 接口(接口可以繼承接口)

接口可以繼承接口 之前我們已經知道接口實現可以從基類被繼承&#xff0c;而接口本身也可以從一個或多個接口繼承而來。要指定某個接口繼承其他的接口&#xff0c;應在接口聲明中把基接口名稱以逗號分隔的列表形式 放在接口名稱后面的冒號之后&#xff0c;如下所示。類在基類列…

linux----------------------線程同步與互斥(上)

1.線程互斥 1-1 進程線程間的互斥相關背景概念 臨界資源&#xff1a;多線程執行流共享的資源就叫做臨界資源 臨界區&#xff1a;每個線程內部訪問臨界資源的代碼就叫做臨界區 互斥&#xff1a;任何時刻&#xff0c;互斥保證只有一個執行進入臨界區&#xff0c;對臨界資源起…

百度AI的開放新篇章:文心4.5本地化部署指南與未來生態戰略展望

百度AI的開放新篇章&#xff1a;文心4.5本地化部署指南與未來生態戰略展望 一起來玩轉文心大模型吧&#x1f449;文心大模型免費下載地址&#xff1a;https://ai.gitcode.com/theme/1939325484087291906 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30…

筆記/sklearn中的數據劃分方法

文章目錄一、前言二、數據劃分方法1. 留出法&#xff08;Hold-out&#xff09;2. K折交叉驗證&#xff08;K-Fold&#xff09;3. 留一法&#xff08;Leave-One-Out&#xff09;三、總結一、前言 簡要介紹數據劃分在機器學習中的作用。 二、數據劃分方法 1. 留出法&#xff0…

Android14 開屏頁SplashScreen設置icon圓角的原理

簡介 我們在看到一個應用在啟動的時候會看到一個啟動的icon,這個圖標是應用的icon當然也是可以應用自己去控制的如 <item name="android:windowSplashScreenAnimatedIcon">@drawable/adas_icon</item> 圖上的效果明顯不理想,圖標是自帶圓角,而且還是…

flutter redux狀態管理

&#x1f4da; Flutter 狀態管理系列文章目錄 Flutter 狀態管理(setState、InheritedWidget、 Provider 、Riverpod、 BLoC / Cubit、 GetX 、MobX 、Redux) setState() 使用詳解&#xff1a;原理及注意事項 InheritedWidget 組件使用及原理 Flutter 中 Provider 的使用、注…

AMIS全棧低代碼開發

amis是百度開源的前端低代碼框架&#xff0c;它通過JSON配置來生成各種后臺頁面&#xff0c;旨在簡化前端開發過程&#xff0c;提高開發效率&#xff0c;降低開發門檻。以下是詳細介紹&#xff1a; 核心特點&#xff1a; 可視化開發&#xff1a;允許開發者通過可視化方式構建頁…