Linux内核从2.6.26开始加入eventfd。
由于eventfd是一种文件描述符,所以我们可以使用基于事件(select、poll、epoll)的方式来唤醒线程进行相关操作,这样比signal开销要小、比unixsock速度要快、还能优化锁颗粒。
相关函数:
#头文件 #include <sys/eventfd.h>
#函数原型 int eventfd(unsigned int initval, int flags);
#函数原型 int eventfd_read(int fd, eventfd_t *val);
#函数原型 int eventfd_write(int fd, eventfd_t val);
/* 64位的计数器 */
typedef uint64_t eventfd_t;
eventfd_t:
其实就是uint64_t;
initval:
表示eventfd_ctx结构体的count使用初始值;
flags:
可接受如下3个参数或组合值:
EFD_CLOEXEC (内核版本2.6.27或以上)
多进程、关闭相关操作会涉及到,具体未测试。
EFD_NONBLOCK (内核版本2.6.27或以上)
将eventfd设置为非阻塞模式,同non-block socket操作方式。
EFD_SEMAPHORE (内核版本2.6.30或以上)
如果未设置此标志位,每做一次read后,val可能为所有已写入到event_fd的值的总和;
如果已设置此标志位,做了多少次write就要进行多少次read;
进一步,我们还能:
1、我们在将数据写入到对方线程时,在通知的机制帮助下可以进一步降低锁的颗粒度;
2、无事件的情况下永久阻塞,这样能减少CPU空转的代码编写;
3、简化事件的编写难度;
由于这里我们只实现一个多线程read、write,我们仅做如下操作:
1、主线程read、子线程write;
2、主线程使用epoll等待事件发生;
3、子线程完成任务后,通过信号让主线程退出。
编译命令:
[root@localhost ~]# gcc -o main main.c -lpthread -std=gnu99
main.c(未设置EFD_SEMAPHORE标志位)
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/eventfd.h>
#include <signal.h>
#include <sys/epoll.h>
pthread_t p,main_p;
int efd;
void work(){
errno = 0;
printf("work thread start...\n");
int number = 1;
for(int i=1;i<10;i++){
int size = eventfd_write(efd, (eventfd_t)number);
printf("i = %d,size = %d,errno = %d\n",i, size,errno);
}
printf("work thread end...\n");
sleep(1);
pthread_kill(main_p,SIGQUIT);
return ;
}
int main(int argc, char *argv[]){
eventfd_t count;
int epfd;
errno = 0;
struct epoll_event ev,events[5];
main_p = pthread_self();
epfd = epoll_create(4);
efd = eventfd(0,EFD_NONBLOCK);
//efd = eventfd(0,EFD_NONBLOCK | EFD_SEMAPHORE);
ev.data.fd = efd;
ev.events = EPOLLIN ;
epoll_ctl(epfd,EPOLL_CTL_ADD,efd,&ev);
pthread_create(&p,NULL,(void*)work,NULL);
for(;;){
int len = epoll_wait(epfd,events,5,-1);
for(int i=0;i<len;i++){
if(events[i].events == EPOLLIN){
eventfd_read(efd,&count);
printf("count = %d\n",count);
}
}
}
return 0;
}
#返回结果:
[root@localhost ~]# gcc -o main main.c -lpthread -std=gnu99
[root@localhost ~]# ./main
work thread start...
i = 1,size = 0,errno = 0
count = 1
count = 1
i = 2,size = 0,errno = 0
i = 3,size = 0,errno = 0
i = 4,size = 0,errno = 0
i = 5,size = 0,errno = 0
i = 6,size = 0,errno = 0
i = 7,size = 0,errno = 0
count = 5
count = 1
i = 8,size = 0,errno = 0
i = 9,size = 0,errno = 0
work thread end...
count = 1
Quit
[root@localhost ~]#
main.c(已设置EFD_SEMAPHORE标志位)
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/eventfd.h>
#include <signal.h>
#include <sys/epoll.h>
pthread_t p,main_p;
int efd;
void work(){
errno = 0;
printf("work thread start...\n");
int number = 1;
for(int i=1;i<10;i++){
int size = eventfd_write(efd, (eventfd_t)number);
printf("i = %d,size = %d,errno = %d\n",i, size,errno);
}
printf("work thread end...\n");
sleep(1);
pthread_kill(main_p,SIGQUIT);
return ;
}
int main(int argc, char *argv[]){
eventfd_t count;
int epfd;
errno = 0;
struct epoll_event ev,events[5];
main_p = pthread_self();
epfd = epoll_create(4);
//efd = eventfd(0,EFD_NONBLOCK);
efd = eventfd(0,EFD_NONBLOCK | EFD_SEMAPHORE);
ev.data.fd = efd;
ev.events = EPOLLIN ;
epoll_ctl(epfd,EPOLL_CTL_ADD,efd,&ev);
pthread_create(&p,NULL,(void*)work,NULL);
for(;;){
int len = epoll_wait(epfd,events,5,-1);
for(int i=0;i<len;i++){
if(events[i].events == EPOLLIN){
eventfd_read(efd,&count);
printf("count = %d\n",count);
}
}
}
return 0;
}
#返回结果
[root@localhost ~]# ./main
work thread start...
i = 1,size = 0,errno = 0
i = 2,size = 0,errno = 0
i = 3,size = 0,errno = 0
i = 4,size = 0,errno = 0
i = 5,size = 0,errno = 0
i = 6,size = 0,errno = 0
i = 7,size = 0,errno = 0
i = 8,size = 0,errno = 0
i = 9,size = 0,errno = 0
work thread end...
count = 1
count = 1
count = 1
count = 1
count = 1
count = 1
count = 1
count = 1
count = 1
Quit
[root@localhost ~]#
未设置EFD_SEMAPHORE标志位时:
每做一次write,number结果如果未立刻读出将会与下一次write结果进行相加(以此类推);
已设置EFD_SEMAPHORE标志位时:
每次read将会与queue操作类似(先进先出、且不会相加);
由于时间与环境原因,未进行更多测试。如有错误,敬请谅解。