深入理解nginx阻塞和非阻塞的不同

让天下没有难学的技术
非阻塞同步算法实战(一)
非阻塞同步算法实战(一)
感谢投递本文。
本文写给对和非阻塞同步算法的实现原理有一定了解,但缺少实践经验的朋友,文中包括了实战中的尝试、所走的弯路,经验和教训。
上个月,我被安排独自负责一个聊天系统的服务端,因为一些原因,我没使用现成的开源框架,网络那块直接使用AIO,收数据时,因为只会从channel里过来,所以不需要考虑同步问题;但是发送数据时,因为有聊天消息的转发,所以必需处理这个同步问题。AIO中,是处理完一个注册的操作后,再执行我们定义的方法,此时,如果还有数据需要写,则继续注册写操作,如果没有则不注册;提交数据时,如果当前没有注册写操作,则注册一个,否则仅提交(此时再注册则会报异常)。这样,需要同步的点就是:如何知道当前还有没有数据需要发送(因为其它线程也可以提交数据到此处),和如何知道此次提交时,有没有注册写操作。总之,要完成:有数据要发送时,必需有写操作被注册,并且只能注册一次;没有数据时,不能有写操作被注册。
经过分析,上面的问题,可以抽象成:我需要知道当往队列里插入一条数据之前,该队列是否为空,如果为空则应该注册新的写操作。当从队列里取出一条数据后,该队列是否为非空,如果非空则应该继续注册写操作。(本文之后以“关注的操作”来表示这种场景下的插入或取出操作)
目前的问题是,我使用的队列是ConcurrentLinkedQueue,但是它的取出数据的方法,没有返回值告诉我们从队列里取出数据之后队列是否为空,如果是使用size或peek方法来执行判断,那就必需加锁了,否则在拿到队列大小时,可能队列大小已经变化了。所以我首先想到的是,如何对该队列进行改造,让它提供该信息。
注意:这里指的不是当次而是之后,所以如果我们使用队列的peek()方法返回null,就知道队列是否为空,但是不知道之后是否为空 ,并且,当关注的操作发生时,在插入或取出操作的返回值里告知此信息,来指导是否继续注册写操作。
如果使用锁的话很容易处理,用锁同步插入和取出方法,下面是锁实现的参考:
public E poll() {
synchronized (this) {
E re = q.poll();
// 获取元素后,队列是空,表示是我关注的操作
if (q.peek() == null) {
public void offer(E e) {
synchronized (this) {
// 插入元素前,队列是空,表示是我关注的操作
if (q.peek() == null) {
q.offer(e);
但因为是服务端,我想用非阻塞同步算法来实现。
尝试方案一
我第一次想到的改造办法是,将head占位节点改成固定的,头节点移动时,只将head节点的next指向新的节点,在插入数据时,如果是在head节点上成功执行的该操作,那么该插入就是关注的的操作;在取出时,如果将head节点的next置为了null,那么该取出就是关注的操作( 因为之前的占位节点是变化的,所以没法比较,必需用同步,现在是固定的了,所以可以直接与head节点进行比较 )。如此一来,问题好像被解决了。改造完之后,出于严谨,我仔细读了一遍代码,发现引入了新的问题,我的取出操作是这样写的
public E poll(){
Node n = head.nextRef.get();//head指向固定的head节点,为final
if(n == null)
Node m = n.nextRef.get();
if(pareAndSet(n,m){
if(m==null)
//此时为关注的操作(为了简化代码显示,不将该信息当作返回值返回了,仅注释)
return n.itemRef.get();
这里有一个致命的问题:如果m为null,在CAS期间,插入了新节点,n的next由null变成了非null,紧接着又把head的next更新为了null,那么链就断了,该方法还存在一些其它的问题,如当队列为空的时候,尾节点指向了错误的位置,本应该是head的。我认为最根本的原因在于,head不能设为固定的,否则会引发一堆问题。第一次尝试宣告失败。
尝试方案二
这次我尝试将head跟tail两个引用包装成一个对象,然后对这个对象进行CAS更新(这仅为一处理论上的尝试,因为从性能上面来讲,已经大打折扣了,创建了很多额外的对象),如果head跟tail指向了同一个节点,则认为队列是空的,根据此信息来判断一个操作是不是关注的操作。但该尝试仅停留在了理论分析阶段,期间发现了一些小问题,没法解决,后来我发现,我把ConcurrentLinkedQueue原本可以分成两步执行的插入和取出操作(更新节点的next或item引用,然后再更新head或tail引用),变成了必需一步完成,ConcurrentLinkedQueue尚不能一步完成,我何德何能,可将它们一步完成?所以直接放弃了。
解决方案一
经过两次的失败尝试,我几乎绝望了,我怀疑这是不是不能判断出是否为关注的操作。
因为是在做项目,周末已经过去了,不能再搞这些“研究”了,所以我退而求其次,想了个不太漂亮的办法,在队列外部维护一个变量,记录队列有多大,在插入或取出后,更新该变量,使用的是AtomicInteger,如果更新时,将变量从1变成0,或从0变成了1,就认为该插入或取出为关注的操作。
private AtomicInteger size = new AtomicInteger(0);
public E poll() {
E re = q.poll();
if (re == null)
old = size.get();
pareAndSet(old,old-1)){
// 获取元素后,队列是空,表示是我关注的操作
if(old == 1){
public void offer(E e) {
q.offer(e);
old = size.get();
pareAndSet(old,old+1)){
// 插入元素前,队列是空,表示是我关注的操作
if(old == 0){
此时,也许细心的朋友会问,因为没有使用锁,这个变量并不能真实反映队列的大小,也就不能确定它是不是关注的操作。没错,是不能真实反映,但是,我获取关注的操作的目的是用来指导我:该不该注册新的写操作,该变量的值变化就能提供正确的指导,所以,同样是正确的,只不过途径不同而已。理论上的分析和后来的项目正确运行都印证了该方法的正确性。
解决方案二
因为上面的方法额外加了一次lock-free级别的CAS操作,我心里总不太舒服,空余时间总在琢磨,真的就没有办法,在不增加额外lock-free级别CAS开支的情况下,知晓一个操作是不是关注的操作?
后来经分析,如果要知晓是不是关注的操作,跟两个数据有关,真实的头节点跟尾节点(不同于head跟tail,因为它们是滞后的,之前将它们包装成一个对象就是犯了该错误),ConcurrentLinkedQueue的实现中,这两个节点是没有竞争的,一个是更新item,一个是更新next,必需得让他们竞争同一个东西,才能解决该问题,于是我想到了一个办法,取出完成后,如果该节点的next为null,就将其用CAS置为一个特殊的值,若成功则认为是关注的操作;插入成功后,如果next被替换掉的值不是null而是这个特殊值,那么该插入也为关注的操作。这仅增加了一次wait-free级别的CAS操作(取出后的那次CAS),perfect!
因为ConcurrentLinkedQueue的很多变量、内部类都是私有的,没法通过继承来改造,没办法,只得自行实现。对于队列里使用的Node,实现的方式有很多种,可以使用AtomicReference、AtomicReferenceFieldUpdater来实现,如果你愿意的话,甚至是像ConcurrentLinkedQueue一样,用sun.misc.Unsafe来实现(注意:一般来说,sun包下的类是不推荐使用的),各有优缺点吧,所以我就不提供该队列的具体实现了,下面给出在ConcurrentLinkedQueue(版本:1.7.0_10)基础上进行的改造,供参考。注意,如果需要用到size等方法,因为特殊值的引入,影响了之前的判断逻辑,应重新编写。
private static final Node MARK_NODE = new Node(null);
public boolean offer(E e) {
checkNotNull(e);
final Node newNode = new Node(e);
for (Node t = tail, p =;) {
Node q = p.
if (q == null || q == MARK_NODE) {//修改1:加入条件:或q == MARK_NODE
// p is last node
if (p.casNext(q, newNode)) {//修改2:将null改为q
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become &live&.
if (q == MARK_NODE)//修改3:
//此时为关注的操作(为了简化代码显示,仅注释)
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
// Lost CAS ra re-read next
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
p = (t != (t = tail)) ? t :
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t :
public E poll() {
restartFromHead:
for (;;) {
for (Node h = head, p = h,;) {
E item = p.
if (item != null && p.casItem(item, null)) {
// Successful CAS is the linearization point
// for item to be removed from this queue.
if (p != h) // hop two nodes at a time
updateHead(h, ((q = p.next) != null) ? q : p);
if (p.casNext(null,MARK_NODE))//修改1:
//此时为关注的操作
else if ((q = p.next) == null) {
updateHead(h, p);
else if (p == q)
continue restartFromH
设计非阻塞算法的关键在于,找出竞争点,如果获取的某个信息跟两个操作有关,那么应该让这两个操作竞争同一个东西,这样才能反应出它们的关系。
原创文章,转载请注明: 转载自本文链接地址:
从事Java开发,对网络编程、多线程、非阻塞算法、正则表达式等比较熟悉,喜欢用OO思想考虑问题,喜欢框架设计。
Latest posts by trytocatch ()
Related posts:
(4 votes, average: 2.25 out of 5)
Loading...&re: 同步/异步与阻塞/非阻塞的区别&&&&
我这个笨还是想不通
排列等待是等待,也是等别人通知你呀,站在你前面那个人的事办完了,还不是就通知到你了。
而那个小纸条是通知,也还是等待啊,还不是等一个个人处理完,最后你前一个处理,就轮到你了。
不是一样啊?没什么分别呀。只不过人排列的顺序变成了号码排列的顺序。
&re: 同步/异步与阻塞/非阻塞的区别&&&&
@ttplay我的理解是:同步的时候,不是站在你前面的人通知你,是你自己得看着你前面还有没有人。而小纸条就不一样了,你拿了纸条就不用管了,出去溜达都可以,反正到时候有人会叫你。
&re: 同步/异步与阻塞/非阻塞的区别&&&&
我觉得IO复用select/epoll那个应该不算异步吧,异步(AIO)是指read/write完成后操作系统会回调用户空间的指定回调方法,而select/epoll只是一个有事件就绪的通知,没有这个回调过程,需要你自己主动调用read/write
&re: 同步/异步与阻塞/非阻塞的区别&&&&
类比很恰当,确实让我又清晰了不少。好文,作者好人。
&re: 同步/异步与阻塞/非阻塞的区别&&&&
@ttplay排队等待是处理消息者自己等待,取小纸条是由别人通知你.
&re: 同步/异步与阻塞/非阻塞的区别&&&&
我的理解:同步: '你'亲自办这件事异步: 交代要做的事情,然后忙其他的事情;'别人'(内核)会充当你的跑腿,在条件就绪后将这事办成,然后通知你(callback);阻塞: 如果条件未就绪,'你'必须死等它就绪;进程睡眠非阻塞:如果条件未就绪,'你'可以转身作别的事情;进程可以作任何想做的事情,不过通常是低效的轮询。以这种理解方式,阻塞/非阻塞只对同步操作有意义;异步I/O总是意味着进程不会因为I/O陷入睡眠。将& select&归类为异步+blocking不妥,select实际上完成的只是read/write的第一部分:等待条件就绪;唯一的改进是可以等待多个条件。&select + read/write&的调用形式容易产生&系统通知我条件就绪&的假象,可实际上你不过是在条件就绪的时候醒来,然后仍然亲自动手完成了数据复制的操作。依然使用银行的隐喻:柜台R:只能取款柜台W:只能存款read: 亲自在柜台R排队(进程睡眠) + 取款write: 亲自在柜台W排队(进程睡眠) + 存款select + read/write : 亲自同时在R、W两个柜台排队(进程睡眠) + (存款|取款|存款+取款)AIO : 告诉心腹小弟要取款若干,然后忙别的事情;小弟取款完毕将其如数奉上。UNP一书中6.2节对I/O模型的分类我觉得很合理:
1).read/write、read + NON_BLOCK、select、signal driven I/O 都属于同步I/O; 它们的共同特点是:将数据从内核空间复制到到用户空间的这个操作,是由用户空间的代码显式发起的。
2).只有AIO 属于 异步I/O;内核不露声色的将数据从内核空间复制到用户空间,然后通知进程。
&re: 同步/异步与阻塞/非阻塞的区别&&&&
@啸天猪我看了一下你的评论,我想我们之间观点最大的分歧点在于:我的观点是同步/异步仅是消息通知的机制,至于消息到来时如何处理与这两个概念无关.而你的观点则认为,同步/异步不仅仅包括消息通知,还包括了对消息的处理,所以select之类的通知消息的触发机制你归类为&同步&,而AIO这种俘获了消息也对消息进行了处理(比如你说的将数据从内核copy到用户态)的机制才是真正的异步.也就是说,你上面回复的这句话:异步: 交代要做的事情,然后忙其他的事情;'别人'(内核)会充当你的跑腿,在条件就绪后将这事办成,然后通知你(callback);事实上是我们之间对这个概念认知的最大分歧,你认为异步就是不止通知了消息,还要加上将这件事情办妥.而我认为,异步仅在于通知这个消息发生了,而具体如何处理该消息不在它关注的范围之内.我在写上上面这段评论的时候也在思考对这个概念的理解,我还是认为我的观点是正确的,今天太晚了,改天找来UNP详细看看.
&re: 同步/异步与阻塞/非阻塞的区别&&&&
概念真要理解了,就是一句话可以说透的。
&re: 同步/异步与阻塞/非阻塞的区别&&&&
@ttplay
在你下面的回复我没看
我理解的是
拿纸条和 排队的区别在于
排队的 必须自己 一直看前面是否有人 没人就是到自己了
拿纸条的就可以这样理解
坐在那里爱 干嘛干嘛 等轮到他时 柜台就给他发个消息 他收到消息就去...
他不用象排队的一样
不断检测 前面是否有人
&re: 同步/异步与阻塞/非阻塞的区别&&&&
同步异步是,一段时间里能不能做多件事情,不能,同步;能,异步。阻塞非阻塞是,自己等待的那个步骤需要不需要自己去确认,需要则是阻塞,不需要则是非阻塞
&re: 同步/异步与阻塞/非阻塞的区别[未登录]&&&&
大哥,你太牛逼了!!
&re: 同步/异步与阻塞/非阻塞的区别&&&&
博主讲得好啊!~!
&re: 同步/异步与阻塞/非阻塞的区别[未登录]&&&&
@那谁select 是同步非阻塞的
&re: 同步/异步与阻塞/非阻塞的区别&&&&
这些都和消息机制基本无关
同步异步是就操作发起者是否实际执行者而言的,是自己亲自去银行取钱还是让小弟帮忙解决
只有异步时才有消息的问题,小弟干完活会向你汇报,同步时你自给自己发消息啊?
阻塞与非阻塞与消息触发也没啥关系,阻塞就是不管银行排多长队等待直到取出钱,非阻塞则是发现人多可以选择不取或不在这儿取(拜托不同于发短信打电话。。。。。不能乱写无用代码)。
select/poll之类也和所谓消息机制无关,要到很多银行分别取一些钱,发现哪家银行人少就去哪家,这显然是同步的,只是需要做很多事情而不是一件。
&re: 同步/异步与阻塞/非阻塞的区别&&&&
靠,纯粹是误人子弟select/poll是同步的,你竟然拿来三番五次的作为异步的例子来讲select/poll机制本来就叫多路复用I/O,或者多路同步I/O
&re: 同步/异步与阻塞/非阻塞的区别&&&&
同意@啸天猪的,select是同步阻塞的
&re: 同步/异步与阻塞/非阻塞的区别[未登录]&&&&
那谁,顶你!写的真不错!
&re: 同步/异步与阻塞/非阻塞的区别&&&&
本来还比较清晰,让这位兄弟这么一说更混了,好像异步和多路I/O复用差的很远吧,扯不上什么关系吧!
&请教大家一个网络客户端设计问题&&&&
我写一个网络客户端sdk,我用的是select多路复用的方式,每一个socket我用一个类封装起来,用户发送一个消息就直接调用send发出去,然后数据接收到的时候会调用我类里面的OnRecvData()函数表示数据接收过来了,
但是我的程序的使用者要求要在发送消息后同步获取返回的数据,这时候最简单的办法是设置一个变量,然后轮训的查消息是否从服务器反馈回来了,但是这样这样肯定是弱智的,
那么如何给客户提供的接口是同步的呢,以下是代码的描述:
我的网络程序的内部//////////////////////////////////////////////////////////////
//一个封装socket的类,select函数返回后会根据不同的socket句柄调用相应的类的接收数据的函数
class Client
{
void OnRecvRead()
recv //这里接收数据
//然后这里根据得到的消息调用不通的消息处理函数
case OnData1Recv()
case OnData2Recv()
case OnData3Recv()
//消息1处理函数
void OnData1Recv()
//接收到反馈数据1,处理数据1
void OnData2Recv()
//接收到反馈数据2,处理数据2
void OnData3Recv()
//接收到反馈数据3,处理数据3
SOCKET m_
}
//驱动select的线程
void ThreadStartService()
{
添加多个client(ip,端口对)到FD_SET结构中
发起非阻塞连接
1:select(....);//循环调用select等待某一个socket可以收消息。
2: 当等待到一个可以读数据的socket后,根据socket查找对应的Client类,然后调用Client的函数OnRecvRead();
OnRecvRead里面再分析协议,然后调用不同的函数。
给客户导出一些函数/////////////////////////////////////////////////////
//启动驱动select的线程
export InitService()
{
StartThread(ThreadStartService);
}
export ProcessData1()
{
1:发数据1处理一个消息,
2:(如何实现?)这里是关键:再这个函数中返回上面“反馈数据1”。
}
export ProcessData2()
{
1:发数据2处理一个消息,
2:(如何实现?)这里是关键:再这个函数中返回上面“反馈数据2”。
}
export ProcessData3()
{
1:发数据3处理一个消息,
2:(如何实现?)这里是关键:再这个函数中返回上面“反馈数据3”。
}
客户使用我的库是先调用InitService,初始化select驱动线程。
可能在任何线程中调用ProcessData1,ProcessData2,ProcessData3,这三个函数。问题是我同步做的很不好。
抱歉写的比较啰嗦,请高人指点!
&re: 同步/异步与阻塞/非阻塞的区别&&&&
写的很好!
&re: 同步/异步与阻塞/非阻塞的区别&&&&
我觉得可以简单一点理解1. 同步调用,就是这个调用结束我要知道结果,不管是成功还是失败2. 异步调用,就是这个调用结束不需要知道结果,结果稍后通知我(回调通知)3. 阻塞,就是调用我,调用线程可能会本挂起4. 非阻塞,就是调用我,调用不会被挂起
&re: 同步/异步与阻塞/非阻塞的区别&&&&
@hansonl ,精辟
&re: 同步/异步与阻塞/非阻塞的区别&&&&
这篇文章,我同意啸天猪的概念。
同步异步我认为不是描述消息如何通知,而是两个动作的处理方式:咱俩并行?还是串行?是顺序?还是并发?如果单讲IO这一步,就不存在同步异步的问题。
比如我们一般的硬盘是同步IO,在允许读写时,cpu要等读写完成后,才能做下一件事情,就是串行;而某些SCSI卡自几有CPU, 如果它肯帮你做读写的话,我们的CPU就可以不管读写动作是否完成,而直接去做下一件事情了。类型猪说的那个父亲让儿子干活的例子。
&re: 同步/异步与阻塞/非阻塞的区别&&&&
&re: 同步/异步与阻塞/非阻塞的区别&&&&
高人帖,看帖得回帖呀!不错,醍醐灌顶
&re: 同步/异步与阻塞/非阻塞的区别[未登录]&&&&
UNP6.2节,跟lz的理解有所不同的说
&re: 同步/异步与阻塞/非阻塞的区别&&&&
楼主刚好说反了,呵呵
&re: 同步/异步与阻塞/非阻塞的区别&&&&
楼主刚好写反了,呵呵
&re: 同步/异步与阻塞/非阻塞的区别&&&&
楼主的描述让我学习到很多,但楼主的观点我不能完全认同。
就拿楼主的银行例子来说,
我归纳一下个人的一些观点,有不当之处希望指正。
1.同步阻塞模式:
a.通过排队等待办理业务,等待过程不做任何其他事情,由等待者自己判断是
否轮到自己。
2.同步非阻塞模式:
a.通过排队等待办理业务,等待过程中打电话、听音乐。
b.通过取号方式等待办理业务,等待过程中打电话、听音乐。
3.异步阻塞模式:
a.通过排队等待办理业务,在排队的时候“创造”一个新的人出来,让这个人
到一边呆着,不做任何事情。
b.通过取号方式等待办理业务,在等待的时候“创造”一个新的人出来,让这
个人到一边呆着,不做任何事情。
(个人认为:异步阻塞模式没有任何意义)
4.异步非阻塞模式:
a.通过排队等待办理业务,在排队的时候“创造”一个新的人出来,让这个人
到一边打电话、听音乐。
b.通过取号方式等待办理业务,在等待的时候“创造”一个新的人出来,让这
个人到一边打电话、听音乐。
所以个人认为:同步和异步的标志是看是否有新的人被“创造”出来。
而阻塞和非阻塞的标志是当同一个人在等待一个事件的时候,是
否可以继续做其他事情。
&re: 同步/异步与阻塞/非阻塞的区别&&&&
1.同步异步,是相对于你的程序框架设计
2.阻塞和非阻塞,相对于一些函数,如recv或send等
明显是不同的概念,两个不同的条件,不能混为一谈
&re: 同步/异步与阻塞/非阻塞的区别&&&&
楼主关于select的阐述有些问题,也是与啸天猪的分歧所在,但是其关于异步/同步,阻塞/非阻塞的观点很易理解也解释的很到位。
select称为“多路复用i/o”,其并不提供将数据从用户空间搬移到内核空间,或从内核空间搬移到用户空间的核心功能,因此并不能将其简单的归类于异步io、或同步io!其要完成io功能,还需要调用者从此函数成功返回后,再调用read,write,因此可将select 和 此后还需调用的read/write看做一个io整体。若select返回正值,则表示此时有设备准备好可以进行io,此时进行io,肯定不会发生阻塞,这时select+read/write,可整体看做一个同步io;至于阻塞还是非阻塞,主要要看select函数的等待时间值设定,若为null,则是阻塞式的;若为非null,则其为非阻塞的,因为其并不能保证能将数据成功写入用户空间或内核空间。select并未设置回调函数,当然不能认为具备异步io功能。
同意你的观点,但可否稍微详细点?
对于io而言,其可以分位同步io,异步io,但是这是属于程序设计的范畴,包括一系列的函数调用,如select,poll,write,read等,并不能简单的将1个函数归类于是异步的还是同步的。
而对于单个io函数,则有阻塞、非阻塞之分。
&re:啸天猪&&&&
更深入的想,啸天猪的观点比较合理:
异步同步不是针对单个函数而言的,是针对程序设计而言的。
如果程序设计要求实现异步io,那楼主理解的异步仅仅是消息通知可以进行io,之后就不管io成功与否就是错误的了,与程序设计偏离了!
&re: 同步/异步与阻塞/非阻塞的区别&&&&
@ttplay你的理解不对。假设有10个业务窗口,你在1窗口排队,那你只能在这慢慢等待轮到自已,这期间你还得慢慢的往前移动(前面有人办完了),还怕别人插队,注意力分散,效率低。而叫号等待就不一样了,10窗口都有可能叫你,你的等待时间大大缩短,等待的时候还可以看看书,玩玩游戏,效率高。
&re: 同步/异步与阻塞/非阻塞的区别[未登录]&&&&
同步和异步应该关注的是两个变化的量的对应关系,在i/o通信中“两个量”分别指什么?
&re: 同步/异步与阻塞/非阻塞的区别[未登录]&&&&
表达的不是很清楚
&re: 同步/异步与阻塞/非阻塞的区别&&&&
select是异步的,但是调用select并不会立刻返回,所以也是阻塞的。
&re: 同步/异步与阻塞/非阻塞的区别&&&&
好文章!清晰了不少!
&re: 同步/异步与阻塞/非阻塞的区别&&&&
建议不要将句子打的太过长,看不完就忘了。
&re: 同步/异步与阻塞/非阻塞的区别&&&&
@ttplay排队你不能离开队伍啊,否则就会被要求重新排队。而拿到号码的话,可以不用一直处于长长的队伍当中。就是区别了吧。就像我们在某一网站上注册时,先填入帐号,再填密码,再点提交。如果是同步的话,需提交后才知道帐号是否已被注册,若是异步的话,填完帐号后就可以知道该帐号是否已被注册了。
&re: 同步/异步与阻塞/非阻塞的区别[未登录]&&&&
楼主观点严重有误,一看啸天猪就是看过UNP.6.2的,不明白的都去看看UNP6.2章节吧,不要在这里模棱两可了,书中讲的很清楚,也非常好理解.看了马上就懂!阻塞io/非阻塞io/多路复用io(select,poll,epoll)/信号驱动io,最终都是要用户进程调用recv来阻塞读取数据的(无论该过程有多么的短暂),所以都归属于posix定义的sync io,async io与syncio的区别就在于用户进程不需要手动调用recv,kernel就为把数据放到用户缓冲区了,用户进程压根不需要干什么事,这就是主要的区别!
阅读排行榜
评论排行榜IO中同步、异步与阻塞、非阻塞的区别-andersonyan-ChinaUnix博客
IO中同步、异步与阻塞、非阻塞的区别
51360阅读 5评论 
一、同步与异步
同步/异步, 它们是消息的通知机制
1. 概念解释
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。
按照这个定义,其实绝大多数函数都是同步调用(例如sin isdigit等)。
但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。
最常见的例子就是 SendMessage。
该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。
当对方处理完毕以后,该函数才把消息处理函数所返回的值返回给调用者。
异步的概念和同步相对。
当一个异步过程调用发出后,调用者不会立刻得到结果。
实际处理这个调用的部件是在调用发出后,
通过状态、通知来通知调用者,或通过回调函数处理这个调用。
以 Socket为例,
当一个客户端通过调用 Connect函数发出一个连接请求后,调用者线程不用等待结果,可立刻继续向下运行。
当连接真正建立起来以后,socket底层会发送一个消息通知该对象。
C. 三种返回结果途径&
执行部件和调用者可以通过三种途径返回结果:
a. & 状态、
b. & 通知、
c. & 回调函数。
可以使用哪一种依赖于执行部件的实现,除非执行部件提供多种选择,否则不受调用者控制。
a. 如果执行部件用状态来通知,
& & 那么调用者就需要每隔一定时间检查一次,效率就很低
& & 有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是一种很严重的错误。
b.&如果是使用通知的方式,
& & 效率则很高,因为执行部件几乎不需要做额外的操作。
c. 至于回调函数,
& & 和通知没太多区别。
2. 举例说明
理解这两个概念,可以用去银行办理业务(可以取钱,也可以存钱)来比喻:
当到银行后,
.可以去ATM机前排队等候 & & & & & & & & & & & & & & & &-- (排队等候)就是同步等待消息
.可以去大厅拿号,等到排到我的号时,
&柜台的人会通知我轮到我去办理业务. & & & & & & &-- (等待别人通知)就是异步等待消息.
在异步消息通知机制中,
等待消息者(在这个例子中就是等待办理业务的人)往往注册一个回调机制,
在所等待的事件被触发时由触发机制(在这里是柜台的人)通过某种机制(在这里是写在小纸条上的号码)
找到等待该事件的人.
在select/poll 等IO 多路复用机制中就是fd,
当消息被触发时,触发机制通过fd 找到处理该fd的处理函数.
3. 在实际的程序中,
同步消息通知机制:就好比简单的read/write 操作,它们需要等待这两个操作成功才能返回;
& & & & & & & & & 同步, 是由处理消息者自己去等待消息是否被触发;
异步消息通知机制:类似于select/poll 之类的多路复用IO 操作,
& & & & & & & & & 当所关注的消息被触发时,由消息触发机制通知触发对消息的处理.
& & & & & & & & & 异步, 由触发机制来通知处理消息者;
还是回到上面的例子,
轮到你办理业务, 这个就是你关注的消息,
而办理什么业务, 就是对这个消息的处理,
两者是有区别的.
而在真实的IO 操作时: 所关注的消息就是 & & 该fd是否可读写,
& & & & & & & & & & &而对消息的处理是 & & 对这个fd 进行读写.
同步/异步仅仅关注的是如何通知消息,它们对如何处理消息并不关心,
好比说,银行的人仅仅通知你轮到你办理业务了,
而办理业务什么业务(存钱还是取钱)他们是不知道的.
二、阻塞与非阻塞
阻塞/非阻塞, 它们是程序在等待消息(无所谓同步或者异步)时的状态.
1. 概念解释
阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。
有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。
对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
socket接收数据函数recv是一个阻塞调用的例子。
当socket工作在阻塞模式的时候, 如果没有数据的情况下调用该函数,则当前线程就会被挂起,直到有数据为止。
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
C. 对象的阻塞模式和阻塞函数调用
对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但是并不是一一对应的。
阻塞对象上可以有非阻塞的调用方式,我们可以通过一定的API去轮询状态,
在适当的时候调用阻塞函数,就可以避免阻塞。
而对于非阻塞对象,调用特殊的函数也可以进入阻塞调用。函数select就是这样的一个例子。
2. 举例说明
继续上面的那个例子,
不论是排队等待,还是使用号码等待通知,
如果在这个等待的过程中,
. 等待者除了等待消息之外不能做其它的事情,那么该机制就是阻塞的,
& 表现在程序中,也就是该程序一直阻塞在该函数调用处不能继续往下执行.
. 相反,有的人喜欢在银行办理这些业务的时候一边打打电话发发短信一边等待,这样的状态就是非阻塞的,
& 因为他(等待者)没有阻塞在这个消息通知上,而是一边做自己的事情一边等待.
三、易混淆的点
很多人也会把异步和非阻塞混淆,
因为异步操作一般都不会在真正的IO 操作处被阻塞,
比如如果用select 函数,当select 返回可读时再去read 一般都不会被阻塞
就好比当你的号码排到时一般都是在你之前已经没有人了,所以你再去柜台办理业务就不会被阻塞.
可见,同步/异步与阻塞/非阻塞是两组不同的概念,它们可以共存组合,
而很多人之所以把同步和阻塞混淆,我想也是因为没有区分这两个概念,
比如阻塞的read/write 操作中,其实是把消息通知和处理消息结合在了一起,
在这里所关注的消息就是fd 是否可读/写,而处理消息则是对fd 读/写.
当我们将这个fd 设置为非阻塞的时候,read/write 操作就不会在等待消息通知这里阻塞,
如果fd 不可读/写则操作立即返回.
四、同步/异步与阻塞/非阻塞的组合分析
_______阻塞____________________非阻塞_____
同步 | 同步阻塞 & & & & & & &同步非阻塞
异步 | 异步阻塞 & & & & & & &异步非阻塞
同步阻塞形式:
& 效率是最低的,
& 拿上面的例子来说,就是你专心排队,什么别的事都不做。
& 实际程序中
& 就是未对fd 设置O_NONBLOCK 标志位的read/write 操作,
异步阻塞形式:
& 如果在银行等待办理业务的人采用的是异步的方式去等待消息被触发,也就是领了一张小纸条,
& 假如在这段时间里他不能离开银行做其它的事情,那么很显然,这个人被阻塞在了这个等待的操作上面;
& 异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息被触发时被阻塞.
& 比如select 函数,
& 假如传入的最后一个timeout 参数为NULL,那么如果所关注的事件没有一个被触发,
& 程序就会一直阻塞在这个select 调用处.
同步非阻塞形式:
& 实际上是效率低下的,
& 想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有,
& 如果把打电话和观察排队的位置看成是程序的两个操作的话,
& 这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的;
& 很多人会写阻塞的read/write 操作,
& 但是别忘了可以对fd 设置O_NONBLOCK 标志位,这样就可以将同步操作变成非阻塞的了;
异步非阻塞形式:
& 效率更高,
& 因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,
& 程序没有在两种不同的操作中来回切换.
& 比如说,这个人突然发觉自己烟瘾犯了,需要出去抽根烟,
& 于是他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下(注册一个回调函数),
& 那么他就没有被阻塞在这个等待的操作上面,自然这个就是异步+非阻塞的方式了.
& 如果使用异步非阻塞的情况,
& 比如aio_*组的操作,当发起一个aio_read 操作时,函数会马上返回不会被阻塞,
& 当所关注的事件被触发时会调用之前注册的回调函数进行处理,
九二,见龙在田,利见大人。
【白话】九二,龙已出现在地上,利于出现德高势隆的大人物。
《象》曰:“见龙在田”,德施普也。
【白话】《象辞》说:“龙已出现在地上”,犹如阳光普照,天下人普遍得到恩惠。
connnect操作实际上是可以工作在同步/异步,阻塞/非阻塞的四种组合中的一种,&br /&你理解的应该是同步阻塞模式,&br /&实际上另三种都是不用等内核层返回结果给用户层后,程序才能继续向下运行。&br /&这篇文章讲了这四种组合:&br /&http://blog.chinaunix.net/uid--id-3754543.html&br /&另外我对socket中应用这四种组合做了专门的示例详解,有兴趣也可以一并看看。&img src=&/image/qqface/13.gif& /&
“当一个客户端通过调用&Connect函数发出一个连接请求后,调用者线程不用等待结果,可立刻继续向下运行。”我的理解connect会进行三次握手,首先发SYN包给服务器,客户端socket进入SYN_SEND状态,等待服务端的ACK,如果收到ACK,立刻进入ESTABLISHED状态,两端可以通信了。如果这个过程出现错误,则错误都会以返回值的形式返回调用者。我这里不太明白你说的“调用者不用等待结果,直接向下执行”是什么意思??大侠能否解释下
看了你的文章后,感觉这里面的概念还是有些自相矛盾的地方,我凭我的理解梳理了一下:/albert1017/p/3914149.html,欢迎指正
北京皓辰网域网络信息技术有限公司. 版权所有

我要回帖

更多关于 深入理解java虚拟机 的文章

 

随机推荐