同步和异步, 阻塞和非阻塞, Reactor和Proactor
http://www.artima.com/articles/io_design_patterns.html
http://note.sdo.com/u/1434409594/n/lvRFW~kjR2N0LX0nE004_r
http://www.cnblogs.com/xuxm2007/archive/2011/08/15/2139808.html, 图示
我看了些blog, 很少有人能说清楚的, 而且很多人的理解根本就不对
通俗的说
阻塞和非阻塞, 等不等的问题, 傻等就是阻塞, 不等就是非阻塞
异步和同步, 是否有并发的问题, 同步只是改变顺序, 现在这个不ready, 先做别的去, 等ready再回来做. 而异步, 我只告诉你干啥然后我干别的去了, 你把事干完后再通知我.(这里的并发不是应用层的多线程并发, 而是只应用层和系统层之间的并发)
再举个简单的例子, 去银行办手续, 人很多
最不济的方法, 排队等, 这就是阻塞的方式
如果选择不等就是非阻塞的方式,
a, 轮询, 每隔段时间来看看人多不, 不多就办, 人多就去干其他的事, 仍然很麻烦需要不断轮询
b, 通知方式(同步方式), 在银行有熟人, 让他在人少的时候打电话通知你, 这就比较高效, 等人少的时候再去办理, 有效节省了等待和轮询的时间
c, VIP方式(异步方式), 到银行直接说我要取多少钱或什么业务, 办好后把钱放在我的保险柜里, 并通知我, 然后该干吗干吗去了.
这个方式最高效, 因为存在并发, 你在做其他事情的时候, 银行的手续也同时办了
通过这个例子可以看出来, 效率越高的方法需要的代价也越大, 没有免费的午餐...
阻塞和非阻塞轮询, 在效率上没有太大的分别, 甚至阻塞可能效率更高些. 这个可以通过日常的生活证明, 很多人都会选择排队等, 而不是过会再来. 所以他们的代价也是基本一样的, 你一个人就可以搞定, 不需要其他人帮助, 在程序中就是只需要client进程就ok
非阻塞的同步方式, 需要有人通知你, 在程序中, 除了client进程, 还需要event demultiplexor进程(负责通知event)
非阻塞的异步方式, 不但需要人通知你, 还需要银行帮你处理业务(前提是银行提供这种服务), 你还需要提供保险箱用来存放办理好的业务和钱. 所以在程序中, 就比较复杂, 需要client进程, 需要event demultiplexor进程(负责通知event), 需要操作系统进程(前提是操作系统支持异步I/O), 还需要分配数据缓存区(保存结果)
再来解释Reactor和Proactor就很容易了, Reactor是非阻塞的同步方式, 而Proactor是非阻塞的异步方式
所以说, 哪种方法更好, 看你的场景, 从复杂度, 程序效率, 耗费, 和OS的支持等方面考虑
TProactor方案, 使用Reactor模拟异步
前面说Proactor虽然好, 但问题在于依赖OS的支持, 比如Unix对于这个的支持就不好, 所以无法使用, 但是又想用异步的方式, 怎么办?
上面的文章提出一种方案, TProactor, 用Reactor来模拟Proactor
具体做法也很简单, 还是银行的例子, 国有银行比较牛比, 不提供代办服务
没事, 我让负责通知我的人来替代银行来帮我办理, 然后再通知我
在程序中, 就是让event demultiplexor完成数据读取, 存放到自定义的缓冲区, 并通知client.
从程序角度说
blocking I/O
阻塞的方式, 进程从调用recvfrom系统调用开始, 一直阻塞, 从wait for data, copy data from kernel to user, 到最终返回
nonblocking I/O, 轮询
I/O multiplexing (select, poll, epoll, kqueue)
这应该是现在最常用的方式, 因为Unix对异步I/O不支持, 所以Reactor设计模式是现在的主流模式
需要两次系统调用, 先使用select返回ready的socket, 然后调用recvfrom读数据
调用select本身是阻塞的, 但select可以同时监控多个I/O, 所以可以理解成是一种非阻塞的方式(不在一个I/O上阻塞)
select作为event demultiplexor发送数据ready的通知
select, poll, epoll, kqueue的区别
select为最基础的版本, 其他都是select的升级版本, 但本质都是一样的
select, 每次都要通过遍历FD_SETSIZE个Socket来check哪些有数据, 所以当监控的socket比较多的时候, 非常低效
poll, select()受限于FD_SETSIZE个句柄, 而poll没有这个限制, 但是仍然是轮询所有socket, 这种方式被称为'level triggered'epoll, kqueue是基于socket的callback的, 所以避免轮询, 当socket状态发生变化时会自动event通知, 缺点是event只会在状态变化时发送一次, 后面如果状态不变化, 就不会发送任何event, 所以如果发生event丢失会比较麻烦. 这种方式称为'edge triggered' (Jonathon Lemon introduced the terms in his BSDCON 2000 paper on kqueue())
signal driven I/O (SIGIO)
只有UNIX系统支持, 通过注册signal和handler, 数据ready时利用系统signal进行通知, 仍是同步方式
asynchronous I/O (the POSIX aio_functions)
纯粹的异步方式, 但好像只有windows的IOCP有比较好的支持
Comparation
可见从左往右, 越来越先进, 至到完全异步, 但是前面也说相应的实现代价也是越来越大……
http://www.kegel.com/c10k.html, The C10K problem翻译