首先介绍阻塞与非阻塞:
阻塞是个什么概念呢?比如某个时候你在等快递,但是你不知道快递什么时候过来,而且你没有别的事可以干(或者说接下来的事要等快递来了才能做);那么你可以去睡觉了,因为你知道快递把货送来时一定会给你打个电话(假定一定能叫醒你)。 非阻塞忙轮询。接着上面等快递的例子,如果用忙轮询的方法,那么你需要知道快递员的手机号,然后每分钟给他挂个电话:“你到了没?”
大部分程序也不会用第二种做法,因为第一种方法经济而简单,经济是指消耗很少的CPU时间,如果线程睡眠了,就掉出了系统的调度队列,暂时不会去瓜分CPU宝贵的时间片了。
为了了解阻塞是如何进行的,我们来讨论缓冲区,以及内核缓冲区,最终把I/O事件解释清楚。缓冲区的引入是为了减少频繁I/O操作而引起频繁的系统调用,当你操作一个流时,更多的是以缓冲区为单位进行操作,这是相对于用户空间而言。对于内核来说,也需要缓冲区。
假设有一个管道,进程A为管道的写入方,B为管道的读出方。
- 假设一开始内核缓冲区是空的,B作为读出方,被阻塞着。然后首先A往管道写入,这时候内核缓冲区由空的状态变到非空状态,内核就会产生一个事件告诉B该醒来了,这个事件姑且称之为“缓冲区非空”。
- 但是“缓冲区非空”事件通知B后,B却还没有读出数据;且内核许诺了不能把写入管道中的数据丢掉这个时候,A写入的数据会滞留在内核缓冲区中,如果内核也缓冲区满了,B仍未开始读数据,最终内核缓冲区会被填满,这个时候会产生一个I/O事件,告诉进程A,你该等等(阻塞)了,我们把这个事件定义为“缓冲区满”。
- 假设后来B终于开始读数据了,于是内核的缓冲区空了出来,这时候内核会告诉A,内核缓冲区有空位了,你可以从长眠中醒来了,继续写数据了,我们把这个事件叫做“缓冲区非满”
- 也许事件Y1已经通知了A,但是A也没有数据写入了,而B继续读出数据,知道内核缓冲区空了。这个时候内核就告诉B,你需要阻塞了!,我们把这个时间定为“缓冲区空”。
这四个情形涵盖了四个I/O事件,缓冲区满,缓冲区空,缓冲区非空,缓冲区非满(说的内核缓冲区)。这四个I/O事件是进行阻塞同步的根本。
阻塞I/O模式下,一个线程只能处理一个流的I/O事件。如果想要同时处理多个流,要么多进程(fork),要么多线程(pthread_create),很不幸这两种方法效率都不高。
于是再来考虑非阻塞忙轮询的I/O方式,我们发现我们可以同时处理多个流了:
while true {
for i in stream[]; {
if i has data
read until unavailable
}
}
我们只要不停的把所有流从头到尾问一遍,又从头开始。这样就可以处理多个流了,但这样的做法显然不好,因为如果所有的流都没有数据,那么只会白白浪费CPU。阻塞模式下,内核对于I/O事件的处理是阻塞或者唤醒,而非阻塞模式下则把I/O事件交给其他对象。
while true {
select(streams[])
for i in streams[] {
if i has data
read until unavailable
}
}
但是使用select,我们有O(n)的无差别轮询复杂度,同时处理的流越多,每一次无差别轮询时间就越长。
epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll之会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的。(复杂度降低到了O(k),k为产生I/O事件的流的个数,也有认为O(1))
- epoll_create 创建一个epoll对象,一般epollfd = epoll_create()
- epoll_ctl (epoll_add/epoll_del的合体),往epoll对象中增加/删除某一个流的某一个事件
比如
epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//有缓冲区内有数据时epoll_wait返回
epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//缓冲区可写入时epoll_wait返回 - epoll_wait(epollfd,...)等待直到注册的事件发生
一个epoll模式的代码如:
while true {
active_stream[] = epoll_wait(epollfd)
for i in active_stream[] {
read or write till unavailable
}
}
你把要监控读写的文件交给内核(epoll_add)
设置你关心的事件(epoll_ctl),比如读事件
然后等(epoll_wait),此时,如果没有哪个文件有你关心的事件,则休眠,直到有事件,被唤醒
然后返回那些事件实现并发,还需要配合非阻塞的读写。这样就可以一下搜集一大把文件(套接字),然后一下读写一大把文件(不会因为某个文件慢而阻塞),这样来实现并发。
仍然用快递例子来说,ePoll的优势就是,你可以随便做其他的事情,当有快递来的时候,他给你打电话让你来拿,你空了的时候下来拿就好了。
不像阻塞那样需要一直在窗边看着快递来没来,也不需要像select那样不停地打电话问快递来没来。尤其是在快递比较多的时候,select需要问快递你没有你的快递,快递说有的时候,你还需要逐个问某一个包裹到没到;ePoll会直接告诉你,你的哪个包裹的快递到了。
kqueue与epoll非常相似,最初是2000年Jonathan Lemon在FreeBSD系统上开发的一个高性能的事件通知接口。注册一批socket描述符到 kqueue 以后,当其中的描述符状态发生变化时,kqueue 将一次性通知应用程序哪些描述符可读、可写或出错了。
kqueue的接口包括 kqueue()、kevent() 两个系统调用和 struct kevent 结构:
- kqueue() 生成一个内核事件队列,返回该队列的文件描述符。其它 API 通过该描述符操作这个 kqueue。
- kevent() 提供向内核注册 / 反注册事件和返回就绪事件或错误事件。
- struct kevent 就是kevent()操作的最基本的事件结构。
struct kevent {
uintptr_t ident; /* 事件 ID */
short filter; /* 事件过滤器 */
u_short flags; /* 行为标识 */
u_int fflags; /* 过滤器标识值 */
intptr_t data; /* 过滤器数据 */
void *udata; /* 应用透传数据 */
};
在一个 kqueue 中,{ident, filter} 确定一个唯一的事件:
- ident
事件的 id,一般设置为文件描述符。
- filter
可以将 kqueue filter 看作事件。内核检测 ident 上注册的 filter 的状态,状态发生了变化,就通知应用程序。kqueue 定义了较多的 filter:
与socket读写相关的filter:
- EVFILT_READ:TCP 监听 socket,如果在完成的连接队列 ( 已收三次握手最后一个 ACK) 中有数据,此事件将被通知。收到该通知的应用一般调用 accept(),且可通过 data 获得完成队列的节点个数。 流或数据报 socket,当协议栈的 socket 层接收缓冲区有数据时,该事件会被通知,并且 data 被设置成可读数据的字节数。
- EVFILT_WRIT:当 socket 层的写入缓冲区可写入时,该事件将被通知;data 指示目前缓冲区有多少字节空闲空间。
行为标志flags:
- EV_ADD:指示加入事件到 kqueue
- EV_DELETE:指示将传入的事件从 kqueue 中移除
过滤器标识值:
- EV_ENABLE:过滤器事件可用,注册一个事件时,默认是可用的。
- EV_DISABLE:过滤器事件不可用,当内部描述可读或可写时,将不通知应用程序。
注册事件到 kqueue
bool Register(int kq, int fd)
{
struct kevent changes[1];
EV_SET(&changes[0], fd, EVFILT_READ, EV_ADD, 0, 0, NULL); int ret = kevent(kq, changes, 1, NULL, 0, NULL); return true;
}
Register 将 fd 注册到 kq 中。注册的方法是通过 kevent() 将 eventlist 和 neventlist 置成 NULL 和 0 来达到的。
人们一般将 socket IO 设置成非阻塞模式,以提高读写性能的同时,避免 IO 读写不小心被锁定。为了达到某种目的,有人会通过 getsocketopt 来偷看 socket 读缓冲区的数据大小或写缓区可用空间的大小。在 kevent 返回时,将读写缓冲区的可读字节数或可写空间大小告诉应用程序。基于这个特性,使用 kqueue 的应用一般不使用非阻塞 IO。每次读时,根据 kevent 返回的可读字节大小,将接收缓冲区中的数据一次性读完;而发送数据时,也根据 kevent 返回的写缓冲区可写空间的大小,一次只发可写空间大小的数据。
文章部分整合知乎上关于epoll和select的回答:https://www.zhihu.com/question/20122137 (原文地址:https://www.cnblogs.com/FG123/p/5256553.html) 补充:另外一篇比较不错的文章:
1.什么是事件复用技术
https://www.cnblogs.com/moonz-wu/p/4740908.html
英文链接:http://people.eecs.berkeley.edu/~sangjin/2012/12/21/epoll-vs-kqueue.html
[转]Kqueue与epoll机制的更多相关文章
-
Kqueue与epoll机制
首先介绍阻塞与非阻塞:阻塞是个什么概念呢?比如某个时候你在等快递,但是你不知道快递什么时候过来,而且你没有别的事可以干(或者说接下来的事要等快递来了才能做):那么你可以去睡觉了,因为你知道快递把货送来 ...
-
【Unix环境编程】select、poll、epoll机制的联系与区别
在linux设计并发网络程序,主要有如下几种模型:Apache模型(Process Per Connection, PPC).TPC(Thread Per Connection)模型,select机制 ...
-
剖析epoll机制
剖析epoll机制 Linux epoll机制; 写这篇文章的原因是, 上次百度面试被问到一个事件怎么添加到epoll的双向链表中的; 这个问题比较深入, 涉及到内核的实现问题, 今天就来理解一下; ...
-
epoll机制详解
epoll机制详解 大牛的详解 epoll详解 什么是epoll? epoll是为处理大批量句柄而作了改进的poll, 是性能最好的多路I/O就绪通知方法; 只有三个系统调用: epoll_creat ...
-
linux epoll机制对TCP 客户端和服务端的监听C代码通用框架实现
1 TCP简介 tcp是一种基于流的应用层协议,其“可靠的数据传输”实现的原理就是,“拥塞控制”的滑动窗口机制,该机制包含的算法主要有“慢启动”,“拥塞避免”,“快速重传”. 2 TCP socket ...
-
从select机制谈到epoll机制
目录 为什么要用select机制 等待队列 唤醒操作 什么是select机制 关于fd_set select使用 poll函数 为什么select效率较低 什么是epoll epoll机制实现思路 e ...
-
select、poll和epoll机制
一.参考网址 1.select函数及fd_set介绍 2.linux select 函数和 fd_set 用法 2.select.poll和epoll的区别 3.利用select实现IO多路复用TCP ...
-
epoll机制
一.参考网址 1.epoll机制:epoll_create.epoll_ctl.epoll_wait.close 2.Linux网络编程 使用epoll实现一个高性能TCP Echo服务器 3.用C写 ...
-
对epoll机制的学习理解v1
epoll机制 wrk用非阻塞多路复用IO技术创造出大量的连接,从而达到很好的压力测试效果.epoll就是实现IO多路复用的关键. 本节是对epoll的本质的学习总结,进一步的参考资料为: <深 ...
随机推荐
-
SQL Server 事务日志传输
概述 可以使用日志传送将事务日志不间断地从一个数据库(主数据库)发送到另一个数据库(辅助数据库).不间断地备份主数据库中的事务日志,然后将它们复制并还原到辅助数据库,这将使辅助数据库与主数据库基本保持 ...
-
Autodesk 最新开发技术研讨会-北京-上海-武汉-成都-西安-PPT下载
经过半月的奔波,转遍祖国大好河山.结论,还是喜欢成都,安逸~,好希望能在成都生活,竹林里品茶,不亦乐乎~ 如果你没能参加我们的研讨会,下面的PPT供参考: 面向世界.面向未来 – Autodesk 云 ...
-
BZOJ 1433 二分图上的博弈
首先对网格染色,发现是而二分图. 那么即在二分图上选一个起点走过的点无法再走,最后无路可走就输了. 如果起点必在最大匹配中,先手必赢. 如果起点不一定在最大匹配中(包括不可能在),后手必赢.网上有解释 ...
-
iconv命令 gbk 转 UTF-8
-----linux gbk 转 UTF-8-------- iconv 用法 iconv -f "gbk" -t "utf-8" < infile &g ...
-
groovy-运算符
算术和条件运算符 Groovy支”!”操作符,例如: 1 def expression = false 2 assert !expression 基于集合的运算符: Spread Operator ( ...
-
论坛类应用双Tableview翻页效果实现
作为一名篮球爱好者,经常使用虎扑体育,虎扑体育应用最核心的部分就是其论坛功能,无论哪个版块,论坛都是其核心,而其论坛部分的实现又别具一格,它以两个tableview的形式翻页滚动显示,而不是常见的那种 ...
-
Java [leetcode 18]4Sum
问题描述: Given an array S of n integers, are there elements a, b, c, and d in S such that a + b + c + d ...
-
使用Delphi声明C++带函数的结构体实战 good
在小组开发中,应用程序部分采用Delphi7,一些组件使用C++做.在今天将一个动态库的C++接口声明头文件转换为D7的Unit单元时,一切都很顺利,直到遇到下面这样一个另类的东西: typedef ...
-
ADFS 2.0 配置简介 PartⅡ – 配置 ADFS 信任关系
ADFS 与应用程序间的各种验证是基于信任关系的,在 ADFS 服务器配置好要信赖的应用程序(以 URL 为标识)后,应用程序再通过指定认证服务器来将用户引导至 ADFS 登录页,登录完成后再将用户的 ...
-
C# devExpress GridControl 行中行 子行 多级行
在工作中经常会碰到需要做行中行,多级行的情况,不熟的情况下,我也只能试着实现. 命名空间 using DevExpress.XtraEditors.Repository; using System.D ...