阻塞式I/O(默认),非阻塞式I/O(nonblock),I/O复用(select/poll/epoll)都属于同步I/O,因为它们在数据由内核空间复制回进程缓冲区时都是阻塞的(不能干别的事)。只有异步I/O模型(AIO)是符合异步I/O操作的含义的,即在1数据准备完成、2由内核空间拷贝回缓冲区后 通知进程,在等待通知的这段时间里可以干别的事。
线程请求IO操作,会阻塞被挂起。
线程请求IO操作,线程继续进行,不会阻塞。
在CreateFile创建设备对象的时候,传入FILE_FLAG_OVERLAPPED参数,告诉系统以异步方式访问设备。调用读写设备函数ReadFile、WriteFile,系统会将IO请求加入到设备驱动程序的队列中,并马上返回。设备驱动程序以最合理的顺序处理队列中的IO请求并非先进先处理。
其中最重要的是OVERLAPPED结构,overlapped的意思是线程的执行与IO操作是同时发生的,执行时间是重叠的。
IO请求处理结束会返回该结构地址,可以创建一个包含OVERLAPPED结构的context结构,保存上下文,根据返回的OVERLAPPED结构地址,通过以下宏可以算出context结构的地址。
#define CONTAINING_RECORD(address, type, field) ((type *)( \
(PCHAR)(address) - \
(ULONG_PTR)(&((type *)0)->field))) field=context结构中的OVERLAPPED结构
缺点:同时发出多个IO请求,例如先read,然后write,最后waitforobject,不知道最后触发的设备内核对象是针对哪个IO请求。
对不同请求创建事件并赋值给OVERLAPPED结构里的hEvent成员变量。
当系统创建线程的时候,会创建与它相关联的异步过程调用队列(asynchronous procedure call,APC)。当发出一个IO请求的时候,可以告诉设备驱动系统,当设备驱动程序完成IO操作,就在APC队列中添加回调函数。
最后线程可用6个函数将自己睡去并设置为可唤醒状态。如果APC队列里有回调函数,系统会唤醒线程并在同线程内执行回调函数,清空队列后,该6个函数就返回。
缺点:1.回调函数提供的上下文少。2.一个线程发出的多个IO请求只能在同一个线程内执行,负载不够均衡。
windows还提供QueueUserAPC函数,允许程序员向线程APC队列里添加回调函数。
创建一个IO完成端口,可以将多个设备及各自的完成键(表明IO请求类型(读文件、写文件或者发送套接字等等))与一个完成端口联系起来,并指定几个线程来处理完成端口中完成队列里的IO请求完成的消息。
线程调用GetQueuedCompletionStatus函数令自己进入睡眠状态,并把自己的线程ID放入等待线程队列,让完成端口内核对象知道有哪些线程在等待IO请求完成,线程等待设备驱动程序处理IO请求并把IO请求的完成键等记录放入IO完成队列后,线程被唤醒,根据IO完成队列里的记录处理IO请求完成后的业务。
IO完成队列里的记录包括已传输的字节数、完成键、错误代码、OVERLAPPED结构地址,通过返回GetQueuedCompletionStatus函数的参数告知线程。
设备列表与等待线程列表
一个进程处理多个IO请求
3.1. select/pselect
监视一系列sockets(文件描述符)并阻塞程序,直到至少有一个准备好时行I/O操作。
需要把fd集合从用户态拷贝到内核态返回时还要从内核态拷贝到用户态
内核会先轮询IO是否完成,如果没有再睡眠进程
select支持的文件描述符数量太小了,默认是1024
3.2. poll
和select类似,不同的是传入fd数组,没有个数限制
3.3. epoll
与select不同,不会轮询IO是否完成,而是IO完成后,通过回调函数将发生的事件写入就绪事件链表,写入用户空间。
epoll_create创建epoll句柄
5.1.信号回调函数机制
5.2.线程回调函数机制
http://blog.csdn.net/mikeszhang/article/details/8891089
http://www.zhihu.com/question/19732473
因为中文语意的问题,很多时候确实会导致混用,而且语境不一样意义也可能不一样。如果只是从计算机编程这个角度说, 讨论最多的也是IO 模型 ,阻塞非阻塞 和 同步异步说的应该是不同的东西。
阻塞非阻塞:可以简单理解为需要做一件事能不能立即得到返回应答,如果不能立即获得返回,需要等待,那就阻塞了,否则就可以理解为非阻塞。 同步异步: 你总是做完一件再去做另一件,不管是否需要时间等待,这就是同步;异步呢则反之,你可以同时做几件事,并非一定需要一件事做完再做另一件事。同步简单理解成一问一答同步进行,异步可以简单理解为不必等一个问题有答了再去问另一个问题,尽管问,有答了再通知你。
2015/2/4 还有人问,这里补充下,其他楼层也有很好的解释,包括从技术角度都有的详细解释。主要搞清楚本质就行了,他们的不同在于:
举个例子: 我去买一本书,立即买到了,这就是非阻塞; 如果恰好书店没有,我就等一直等到书店有了这本书买到了才走,这就是阻塞; 如果书店恰好没有,我就告诉书店老板,书来了告诉我一声让我来取或者直接送到我家,然后我就走了,这就是异步。 那同步呢? 前面两种情况,非阻塞和阻塞都可以称为同步。 如果说书店有这书,我还让老板通知我以后来取就没这个必要了。
反映在编程方面就是 用户进程 调用 系统调用。(用户进程对应我,内核 对应 书店老板,书对应数据资源data , 买书就是一个系统调用了) 这阻塞非阻塞与同步异步IO机制,都是伴随计算机系统发展,用来解决一些出现的问题。阻塞非阻塞、同步异步可以组合,但是没必要组合,应该说是不同的IO机制,没必要纠结怎么区分,如果定要组合心里才爽,可以 这样认为:阻塞非阻塞都是同步,异步就没什么阻塞不阻塞了,都异步了还阻塞啥,肯定是非阻塞了。(异步非阻塞听起来多别扭)
unix网络编程中说到: 将IO模型分为五类:阻塞IO,非阻塞IO,IO复用,信号驱动,异步IO 其中阻塞IO就是那种recv, read,一直等,等到有了拷贝了数据才返回; 非阻塞就是不用等,立即返回,设置描述符为非阻塞就行了,但是要进程自己一直检查是否可读; IO复用其实也是阻塞的,不过可以用来等很多描述符; 信号驱动采用信号机制等待; 异步IO就不用等待了,当他告知你的时候,已经可以返回了,数据都拷贝好了。
posix.1严格定义的异步IO是要求没有任何一点阻塞,而上述的前面四个(阻塞IO,非阻塞IO,IO复用,信号驱动)都不同程度阻塞了,而且都有一个共同的阻塞: 内核拷贝数据到进程空间的这段时间需要等待。