事件通知-eventfd的简单使用
eventfd
是linux
提供的用于事件通知的一种文件描述符,可以用来当作wait、notify
的一种工具,用其可以轻易实现线程间或进程间的信号共享。eventfd
维护了一个uint64_t
类型的一个计数器,因此,对于他的读写而言,也只能进行uint64_t
的读写。eventfd
虽然是一个文件描述符,但它本身并非是一个真实的文件,它存在的目的就是用来实现消息通知的。
对于eventfd
的读写只能读写uint64_t
类型的数字,write
时会将写入的值与内部计数器进行累加,例如同时write
两次值为3的数字,然后再去读的时候只会读一次,读到的值为6,读完后会将计数器置为0,此时不可读。
int eventfd(count, flag)
该方法用于创建一个文件描述符,其第一个参数是初始值,第二个参数是对应的flag
,可以用‘或’操作设置多个flag
。返回值则是该文件描述符。
EFD_NONBLOCK
创建非阻塞式的文件描述符。对于eventfd
而言,若是当前没有被写入内容,即内部计数器值为0,此时对其进行读取的时候,线程会阻塞在read
方法中。若是设置了EFD_NONBLOCK
,read
的时候就会直接返回,不论是否有值。若是有值,则返回uint64_t
的大小,否则返回-1。EFD_SEMAPHORE
创建信号量类型的文件描述符。当设置为EFD_SEMAPHORE
后,eventfd
则不再是一个普通的读写通知,而是作为信号的读写通知。即之前是write
一个数字后,内部计数器会将数字累加,可以通过read
将该数字读取出来。而设置该flag
后,write
的值会作为信号的个数在内部计数器上累加,而read
每次只能读取1,读完之后计数器减1。例如写入一个数字3,则可以连续读取3次,每次read
到的值都是1。EFD_CLOEXEC
write & read
#include <unistd.h>
ssize_t write (int __fd, const void *__buf, size_t __n)
ssize_t read (int __fd, void *__buf, size_t __nbytes)
读写没什么特殊的,就是像普通的文件读写是一样的,但是需要注意的是,读写的内容是有要求的,即只能是uint64_t
类型的,否则会读取或写入失败。返回值也是一样的,若是读写成功,则返回读写的大小,即sizeof(uint64_t)
,失败则返回-1。
默认情况下,读写都是阻塞式的。例如eventfd
是不可读的时候,没有内容写入,内部计数器值为0,此时read
方法会阻塞当前线程,直到可读的时候才会返回。可以通过创建eventfd
的时候传入EFD_NONBLOCK
的flag,此时则是非阻塞式的,若是不可读写,则并不会阻塞而是直接返回-1。
eventfd_write & eventfd_read
#include <sys/eventfd.h>
typedef uint64_t eventfd_t;
int eventfd_read (int __fd, eventfd_t *__value);
int eventfd_write (int __fd, eventfd_t __value);
前面read、write
方法是普遍的对于文件描述符的读写,实际上eventfd
对于读写也进行了封装,即上面的两个方法。该方法明确了读写的值为eventfd_t
类型,即uint64_t
,避免读写时类型写错。读写成功的话返回值为0,否则为-1。
示例
#include <sys/eventfd.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
void *child_write(void *data) {
int *efd = data;
pthread_t tid = pthread_self();
// 子线程负责写入
for (int i = 0; i < 5; ++i) {
eventfd_t number = 3;
int value = write(*efd, &number, sizeof(uint64_t));
if (value == sizeof(uint64_t)) {
printf("Child Write Success, tid: %lu\n", tid);
} else {
printf("Child Write Fail, tid: %lu\n", tid);
}
sleep(1);
}
return 0;
}
int main(int argc, char *argv[]) {
int efd = eventfd(0, EFD_CLOEXEC);
if (efd < 0) {
printf("create efd error. \n");
return -1;
}
printf("efd: %d,\n", efd);
// 创建子线程进进行写入
pthread_t child_thread;
pthread_create(&child_thread, 0, child_write, &efd);
if (child_thread < 0) {
printf("create thread error.\n");
close(efd);
return -1;
}
int count = 0;
while (1) {
uint64_t number = 0;
int len = read(efd, &number, sizeof(uint64_t));
if (len == sizeof(uint64_t)) {
// 具体处理逻辑
printf("read success: %lu, len = %d, count = %d\n", number, len, ++count);
} else {
printf("read fail.\n");
}
}
close(efd);
return 0;
}
上述代码实现了主线程循环读取数据,若是不可读的话,则会阻塞在read
方法中。然后子线程每隔1秒会向eventfd
中写入一个值,然后主线程会被唤醒,然后读取到这个值后再次阻塞在read
中,继续等待被唤醒。由于eventfd
计数器的特性,每次写入的值会被累加,所以不适合多个线程同时写入。
eventfd
不适合多个线程同时写入的意思是说无法准确读取到多个线程写入的值,毕竟它本身也只是用来实现wait/notify
的。若仅是用来实现等待、唤醒机制的话,eventfd还是很很合适的。