关于eventfd,epoll,线程间通信小记

先介绍eventfd

1 #include<sys/eventfd.h>
2 int eventfd(unsigned int initval, int flags);

使用这个函数来创建一个事件对象,linux线程间通信为了提高效率,大多使用异步通信,采用事件监听和回调函数的方式来实现高效的任务处理方式(虽然会将逻辑变得复杂)。

linux内核会为这个事件对象维护一个64位的计数器(uint64_t).并在初始化时用传进去的initval来初始化这个计数器,然后返回一个文件描述符来代表这个事件对象。

第二个参数是描述这个事件对象的属性,可以设置为EFD_NONBLOCK , EFD_CLOEXEC;前面的是设置对象为非阻塞状态,如果没有设置为非阻塞状态,read系统调用来读这个计数器,且计数器的值为0时,就会一直阻塞在read系统调用上,反之如果设置了该标志位,就会返回EAGAIN错误。后面的EFD_CLOEXEC功能是在程序调用exec()函数族加载其他程序时自动关闭当前已有的文件描述符(具体为什么暂不解释)。

通过此函数得到的对象既然是一个计数器,我们就可以对它进行读和写:

使用write将缓冲区写入的8字节整形值加到内核计数器上。

使用read将内核计数的8字节值读取到缓冲区中,并把计数器重设为0,如果buffer的长度小于8字节则read会失败,错误码设为EINVAl。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

再介绍epoll,忍不住的可以直接向下翻

epoll是对select,poll这种IO多路转接方式的改进

接口:  int epoll_create(int intsize);

          int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

          int epoll_wait(int epfd, struct epoll_event* events,int maxevents, int timeout);

工作模式:

  水平触发:缺省的工作方式,并且同时支持block和no-blocksocket,在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表

  边缘触发:高速工作方式,只支持no-blocksocket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了

用途:使用epoll_wait对某个文件描述符进行事件监听,监听到事件后会返回相关的结构体,得到其中有事件到来的fd,使用对应的回调函数(手动实现fd到回调函数的映射)来处理该fd上的事件:读数据或者写数据之类的。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

下面进入使用场景:

原始做法:(会有bug,下面分析)

初始化:先生成一个eventfd,初始化计数器为0,此eventfd可以通过一些方法在下面两个线程间共享

线程A:处理一些来自外部的请求,每处理完一个请求后会向eventfd的计数器中写入处理的结果,是一个整型值,然后接着处理下一个请求。

线程B:对eventfd进行Epoll监听,回调函数的功能是对eventfd的计数器读数据出来并将结果进行分发。

用例1:外部单个客户端每隔1秒向线程A发送一个请求。

用例1结果:线程A正确处理请求,并将结果写入eventfd中,线程B及时从eventfd中读取出请求处理结果,并正确分发给其他线程。

用例2:外部单个客户端连续向线程A发送多个请求。

用例2结果:线程A正确处理请求,并正确地将结果写入eventfd中,但在一定概率的情况下,线程B从eventfd中读到的结果不是线程A一次写入的结果,而是多次写入的结果。因此不能正确的分发请求。线程B中epoll捕捉到的事件次数小于线程A写入产生的事件数量。

用例3:外部多个客户端同时向线程A发送一个请求

用例3结果:线程A正确处理请求,并正确的将结果写入eventfd中,在很大的概率情况下,线程B中eventfd中读到的结果不是线程A一次写入的结果,而是多次写入的结果。因此,也不能正确的分发请求。线程B中epoll捕捉到的事件次数小于线程A写入产生的事件数量。

BUG分析:在这个场景中,线程A和线程B分别相当于生产者和消费者,只从原始生产者消费者模型上看并没有问题,满足数据为空时读不到数据,数据满时写不进数据(read,write的功能),但是在当前场景中,加了一个特别的要求:每次写入的数据应该可以被独立识别而不是累加,每次写入的事件也应该被epoll独立的捕捉到。因此,需要对事件和数据各自进行序列化上的拆分。

改进做法:

初始化:先生成一个eventfd,初始化计数器为1,再生成一个空队列Q和互斥锁,此eventfd,队列Q和互斥锁可以通过一些方法在下面两个线程间共享,

线程A:处理一些来自外部的请求,每处理完一个请求后会从eventfd的计数器read数据,加1之后再write,将处理结果写入到队列末尾,然后接着处理下一个请求。

线程B:对eventfd进行Epoll监听,回调函数的功能是对eventfd的计数器read数据出来然后判断,如果大于1就自减1然后从队列头部取出数据,并将结果进行分发
,最后再写入新的计数器数据。如果等于1那么就直接返回,代表没有新的数据到来。

用例1,2,3在此环境下均可正常跑通。

回过头来分析原始做法的fatal error在哪:

作为生产者的线程A没有向线程B解释自己向eventfd中写入了多少个数据,产生了多少次事件。

作为消费者的线程B一次read就把eventfd中所有的数据当做一个数据读了出来,却没有相关依据来对读出来的数据做拆分。

作为通信工具的eventfd只能将数据进行累加,起到计数器的作用而不能存储实际数据。

作为消息监听的epoll在水平触发模式下只能通知是否有事件而不能通知有多少事件,在边缘触发下不能保留每次事件的产生都能及时被消费者捕获到。

因此,改进做法是将事件的多少通过计数器来表达,将实际传输的数据通过FIFO队列来传达。

Happy Ending Every Day.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Flutter入门到实战

一行代码快速解耦Application逻辑,让Application更简洁好维护

版权声明:本文为博主原创文章,未经博主允许不得转载。https://www.jianshu.com/p/23b9ba9b685d

713
来自专栏我是攻城师

关于kafka连接的一个小问题

3334
来自专栏小灰灰

Logback 简明使用手册

主要内容 跑起来 配置项 工作原理 1. 统一日志输出 巴拉巴拉。。。 (为什么这么玩,有什么好处 google it) 说明: 本篇主要偏应用为主,介绍了如...

2719
来自专栏coder修行路

关于go语言的测试相关内容笔记

其实之前对于测试自己一直比较弱,不管是python的还是go的,关于测试这块并没有非常注重,这次就好好整理一下关于go的测试

701
来自专栏北京马哥教育

Python爬虫基础知识:urllib2的使用技巧

糖豆贴心提醒,本文阅读时间6分钟 前面说到了urllib2的简单入门,下面整理了一部分urllib2的使用细节。 1.Proxy 的设置 urllib2 默认...

2855
来自专栏SDNLAB

Ryu:模块间通信机制分析

Ryu是一款非常轻便的SDN控制器,在科研方面得到了广泛的应用。相比其他控制器,受益于Python语言,在Ryu上开发SDN应用的效率要远高于其他控制器。为了解...

3438
来自专栏张戈的专栏

Shell脚本的简单排错法及调试程序bashdb

Jboss 的研究稍有卡壳,那就来点基础教程好了。 与众多脚本语言一样,Shell 脚本在执行时出错是很常见的,最简单的原因无外乎脚本在编写的过程中出现了语法错...

3766
来自专栏Java技术分享

dubbo 到底是用来干嘛的?

一个一个回答吧。 1. 负载均衡:对外提供一个公共地址,请求过来时通过轮询、随机等,路由到不同server。目的是分摊压力。    失效备援:发现一台serve...

27410
来自专栏互联网技术杂谈

beanstalkc Tutorial 中文版

英文原版:https://github.com/earl/beanstalkc/blob/wip-doc-rtfd/doc/tutorial.rst

3028
来自专栏Coding迪斯尼

java开发系统内核:创建文件操作API

1333

扫码关注云+社区