锁定老帖子 主题:同步/异步及阻塞/非阻塞
精华帖 (0) :: 良好帖 (5) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2013-08-06
最后修改:2013-08-07
以下是摘自http://www.ibm.com/developerworks/cn/java/j-lo-javaio的文章, 同步与异步 所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。而异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。我们可以用打电话(同步)和发短信(异步)来很好的比喻同步与异步操作。 在设计到 IO 处理时通常都会遇到一个是同步还是异步的处理方式的选择问题。因为同步与异步的 I/O 处理方式对调用者的影响很大,在数据库产品中都会遇到这个问题。因为 I/O 操作通常是一个非常耗时的操作,在一个任务序列中 I/O 通常都是性能瓶颈。但是同步与异步的处理方式对程序的可靠性影响非常大,同步能够保证程序的可靠性,而异步可以提升程序的性能,必须在可靠性和性能之间做个平衡,没有完美的解决办法。 阻塞与非阻塞 阻塞与非阻塞主要是从 CPU 的消耗上来说的,阻塞就是 CPU 停下来等待一个慢的操作完成 CPU 才接着完成其它的事。非阻塞就是在这个慢的操作在执行时 CPU 去干其它别的事,等这个慢的操作完成时,CPU 再接着完成后续的操作。虽然表面上看非阻塞的方式可以明显的提高 CPU 的利用率,但是也带了另外一种后果就是系统的线程切换增加。增加的 CPU 使用时间能不能补偿系统的切换成本需要好好评估。 我的理解: 引用 打个比喻吧:司机开车在路上阻车了,如果采用阻塞模式,则司机停下来什么事也不干,就眼巴巴地直等前面的车启动,他继续跟车。如果采用非阻塞模式,则前面阻车后,司机停下来看报纸,听音乐,等前面车走动后,他再放下报纸,继续跟车。
显然,非阻塞式司机的时间利用率提高了,而阻塞式司机时间白白浪费了,前者往往是乐天派,而后者是抱怨派。 但是个人总觉得,这两段话还是没有把“同步与异步”与“阻塞与非阻塞”的概念说清楚,我有以下几点疑问: 1)同步与异步 和 阻塞与非阻塞 所描述的主体是什么,是否前者描述是主体是调用序列(或说两个任务),而后者描述主体是CPU的调度呢? 2)同步与异步和阻塞与非阻塞的真正区别是什么,总觉得同步是对应阻塞,而异步是对应非阻塞的。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2013-08-07
最后修改:2013-08-07
同步与异步是站在任务调用者角度而言的。
任务调用者调用的操作跟任务调用者自身的操作不是顺序(同步)的操作,这个调用就是异步的操作。异步操作背后的实现往往采用多线程(看语言对任务的封装粒度)。 用Java代码表示就是: A: public class A extends Thread { public void run() { //各种操作 } public void call() { start(); } } B: public class B { public void operation() { //各种操作 } public void call() { new A().call(); //与调用者B后续的操作不是顺序执行的 operation(); } } 阻塞与非阻塞是站在CPU角度来看的。计算机各个部件的运行速度是不一样的,其中CPU速度最快。早期CPU与内存速度是一致的,访问内存并不需要额外等待,这里把CPU与内存视为一个整体作为计算机的内部系统——内设。而其它设备——外设的运行速度就很慢了。 当计算机要做的下一个事情不是仅在内设中就能直接处理的,需要外设的参与时,CPU会将事情交给外设去做。如果内设等待慢腾腾的外设处理完成并返回后才接着做后边的事情,这种模式就是阻塞模式;反之CPU将事情交给外设之后,内设就直接去做后边的事情不等待,这种模式就是非阻塞模式。所以阻塞与非阻塞发生的场景就是内设与外设交互之时。 内设与外设的交互往往被抽象为IO,所以阻塞与非阻塞几乎总是伴随IO出现(但这不是必须的)。 由此可见,同步/异步与阻塞/非阻塞是两组无关的概念。 同步阻塞很常见,就不再列举了。 异步阻塞的Java代码表示:基于上边的代码,仅改动A中run的代码。 public void run() { try{ ServerSocketChannel ssc = ServerSocketChannel.open(); ServerSocket socket = ssc.socket(); InetSocketAddress add = new InetSocketAddress(11111); socket.bind(add,2); ssc.configureBlocking(true); //使用阻塞模式,默认行为 while(true) { SocketChannel accept = ssc.accept(); //这里会阻塞 if(accept!=null) { System.out.println(accept); }else { System.out.println("hehe"); Thread.sleep(500); } } }catch(Exception ex){} } 那么B的call()执行时,会明显的发生异步阻塞。 异步非阻塞的Java代码表示(如果A不是多线程程序,就成为了同步非阻塞): public void run() { try{ ServerSocketChannel ssc = ServerSocketChannel.open(); ServerSocket socket = ssc.socket(); InetSocketAddress add = new InetSocketAddress(11111); socket.bind(add,2); ssc.configureBlocking(false); //使用非阻塞模式 while(true) { SocketChannel accept = ssc.accept(); //这里不会阻塞 if(accept!=null) { System.out.println(accept); }else //执行其它的事情 { System.out.println("hehe"); //会看到这一步 Thread.sleep(500); } } }catch(Exception ex){} } 然后你分别使用telnet 127.0.0.1 11111,测试看看不同行为。 --------- 补充一下,上述的回答是按LZ提供的上下文考虑的。 在阻塞/非阻塞中,如果把CPU视为任务调用者,那么非阻塞中外设相对内设是异步的。而同步/异步中,异步操作在调用者看来也总是不会阻塞的,但站在执行者或CPU的角度来看这一个操作就不一定不会阻塞了。 |
|
返回顶楼 | |
发表时间:2013-08-07
楼上解释的蛮好。
这里贴一部分线程调度的东西,希望对你理解有帮助。 线程调度 当若干进程都有多个线程时,就存在两个层次的并行:进程和线程。在这样的系统中调度处理有本质差别,这取决于所支持的是用户级线程还是内核级线程(或两者都支持)。 首先考虑用户级线程。由于内核并不知道有线程存在,所以内核还是和以前一样地操作,选取一个进程,假设为A,并给予A以时间片控制。A中的线程调度程序决定哪个线程运行,假设为A1。由于多道线程并不存在时钟中断,所以这个线程可以按其意愿任意运行多长时间。如果该线程用完了进程的全部时间片,内核就会选择另一个进程运行。 在进程A终于又一次运行时,线程A1会接着运行。该线程会继续耗费A进程的所有时间,直到它完成工作。不过,该线程的这种不合群的行为不会影响到其他的进程。其他进程会得到调度程序所分配的合适份额,不会考虑进程A内部所发生的事。 现在考虑A线程每次CPU计算的工作比较少的情况,例如,在50ms的时间片中有5ms的计算工作。于是,每个线程运行一会儿,然后把CPU交回给线程调度程序。这样在内核切换到进程B之前,就会有序列A1,A2,A3,A1,A2,A3,A1,A2,A3,A1。这种情形可用图2-43a表示。 实时系统使用的调度算法可以是上面介绍的算法中的任意一种。从实用考虑,轮转调度和优先级调度更为常用。惟一的局限是,缺乏一个时钟将运行过长的线程加以中断。 现在考虑使用内核级线程的情形。内核选择一个特定的线程运行。它不用考虑该线程属于哪个进程,不过如果有必要的话,它可以这样做。对被选择的线程赋予一个时间片,而且如果超过了时间片,就会强制挂起该线程。一个线程在50ms的时间片内,5ms之后被阻塞,在30ms的时间段中,线程的顺序会是A1,B1,A2,B2,A3,B3,在这种参数和用户线程状态下,有些情形是不可能出现的。这种情形部分通过图2-43b刻画。 用户级线程和内核级线程之间的差别在于性能。用户级线程的线程切换需要少量的机器指令,而内核级线程需要完整的上下文切换,修改内存映像,使高速缓存失效,这导致了若干数量级的延迟。另一方面,在使用内核级线程时,一旦线程阻塞在I/O上就不需要像在用户级线程中那样将整个进程挂起。 从进程A的一个线程切换到进程B的一个线程,其代价高于运行进程A的第2个线程(因为必须修改内存映像,清除内存高速缓存的内容),内核对此是了解的,并可运用这些信息做出决定。例如,给定两个在其他方面同等重要的线程,其中一个线程与刚好阻塞的线程属于同一个进程,而另一个线程属于其他的进程,那么应该倾向前者。 另一个重要因素是用户级线程可以使用专为应用程序定制的线程调度程序。例如,考虑图2-8中的Web服务器。假设一个工作线程刚刚被阻塞,而分派线程和另外两个工作线程是就绪的。那么应该运行哪一个呢?由于运行系统了解所有线程的作用,所以会直接选择分派线程接着运行,这样分派线程就会启动另一个工作线程运行。在一个工作线程经常阻塞在磁盘I/O上的环境中,这个策略将并行度最大化。而在内核级线程中,内核从来不了解每个线程的作用(虽然它们被赋予了不同的优先级)。不过,一般而言,应用定制的线程调度程序能够比内核更好地满足应用的需要。 |
|
返回顶楼 | |
发表时间:2013-08-07
以前的老java自己实现了线程库,也就是说java的线程并不和操作系统的线程对应,jvm在操作系统上面是一个进程,当这个进程被操作系统调度到后,jvm内部实现的线程库再调度java线程,为什么是这样呢?考虑到以前的操作系统内核,比如linux,在以前都不直接支持线程,用户线程和内核线程是多对一的关系,solaris一度也是这样,所以java当然心有余而力不足了,你操作系统都不能完美支持线程,你让我实现不是难为我吗?在那个年代,java多线程的调度完全是自主的,操作系统根本不知道java是多线程的,调度策略完全自己实现,单cpu下肯定是分时的,多cpu下就看jvm会不会建立多cpu上的多jvm实例了。
到了后来,操作系统内核纷纷都支持了多线程(windows开始就支持),那么java也要考虑推卸一些责任了,这样java线程就和操作系统线程一一对应或多多对应了,这个时候,如果是一一对应,那么线程的调度完全交给了操作系统内核,当然jvm还保留一些策略足以影响到其内部的线程调度,举个例子,在linux下,只要一个Thread.run就会调用一个fork产生一个线程。 下面回答你的问题: 问:java获得cup使用权采用的抢占机制,使用cup的时候是分时机制,这句话对不对? 答:部分对,早期实现,基本可以实现抢占式,但是现代实现,如果系统不支持抢占,那么jvm也无所谓抢占了。 问:多线程使用cup和使用的操作系统有关还是java机制有关(xp是什么机制) 答:早期是java机制实现,现在大部分是操作系统实现的,java机制仅仅保留了相关策略从而影响调度;xp是基于优先级的抢占式调度,其性能很大程度依赖于动态优先级提升 |
|
返回顶楼 | |
发表时间:2013-08-07
最后修改:2013-08-07
感谢runshine的精彩回复!你这样解释,确实清楚多了。
我的小结是: 1.同步/异步针对任务调度来说的,其实现机制一般是多线程,异步情况即A任务调用B任务时,A任务在A线程上执行,而B任务在B线程上执行; 2.阻塞/非阻塞一般是针对IO来说的,快慢双方在交互时,如果快的一方停下来啥事不干专等慢的一方完成(苦逼型的从一而终 ),即阻塞,快的一方在慢的一方未完成时,干其它的事,慢的一方完成后,再回过头来处理慢的一方的数据,即为非阻塞(快乐型的脚踏多船 ) 同步/异步的实现机制是多线程,由于阻塞/非阻塞存在快的一方同步处理多个慢的一方,感觉其内部实现机制应该也是多线程吧(类似于一个扫描监控线程在快和慢的双方进行通知),谁能说说阻塞/非阻塞的内部真正的实现机制呢? |
|
返回顶楼 | |
发表时间:2013-08-07
最后修改:2013-08-07
stamen 写道 感谢runshine的精彩回复!你这样解释,确实清楚多了。
我的小结是: 1.同步/异步针对任务调度来说的,其实现机制一般是多线程,异步情况即A任务调用B任务时,A任务在A线程上执行,而B任务在B线程上执行; 2.阻塞/非阻塞一般是针对IO来说的,快慢双方在交互时,如果快的一方停下来啥事不干专等慢的一方完成(从一而终 ),即阻塞,快的一方在慢的一方未完成时,干其它的事,慢的一方完成后,再回过头来处理慢的一方的数据,即为非阻塞(脚踏多船 ) 同步/异步的实现机制是多线程,由于阻塞/非阻塞存在快的一方同步处理多个慢的一方,感觉其内部实现机制应该也是多线程吧(类似于一个扫描监控线程在快和慢的双方进行通知),谁能说说阻塞/非阻塞的内部真正的实现机制呢? 谁能说说阻塞/非阻塞的内部真正的实现机制呢? IO的话,这个就要看对应的CPU体系支持的情形以及OS的具体实现了。常见的来说,就是中断,以及DMA。 普通的函数调用,其实本来就是阻塞的啊 A.operation1(); A.operation2(); 两个函数操作了相同的东西(防止乱序,保证语义的顺序性),CPU又就只有一个,operation1在未执行结束前,operation2必然不会执行 并且要注意的是,这里异步的主体是任务。还有个专门的异步IO模型,这个说起来又比较复杂了,大约的思想就是,在非阻塞的内设与外设(即异步的两组设备)基础上,任务完成后直接由操作系统内核回调注册的函数。如果始终站在硬件的角度来,基本常见的就还是中断或其它硬件体系实际支持的设备交互时相互通讯的模型。这个是为了避免非阻塞IO模型需要依靠轮询(就是我例子中那样)来观察外设任务的结束而无谓浪费掉CPU资源。 |
|
返回顶楼 | |
发表时间:2013-08-07
to:runshine
按你这样说,是否可以理解为非阻塞的真正内部实现机制是JVM 调用OS内核来实现的,而非JVM内部机制来整的? |
|
返回顶楼 | |
发表时间:2013-08-07
stamen 写道 to:runshine
按你这样说,是否可以理解为非阻塞的真正内部实现机制是JVM 调用OS内核来实现的,而非JVM内部机制来整的? 跟外设交互的事情基本是这样,但说是“调用OS内核来实现”不太准确。 同外设交互基本都要通过驱动程序,系统会提供相应调用接口,在java中最终必然要靠native方法包装。但不一定是system call(核态),也可能是普通的API调用(用户态)。因为某些在操作系统上驱动程序工作在核态,某些操作系统上驱动程序工作在用户态。准确些的说法是“调用OS提供给用户的接口来实现”,你应该也是这个意思。 |
|
返回顶楼 | |
发表时间:2013-08-08
runshine 写道 stamen 写道 to:runshine
按你这样说,是否可以理解为非阻塞的真正内部实现机制是JVM 调用OS内核来实现的,而非JVM内部机制来整的? 跟外设交互的事情基本是这样,但说是“调用OS内核来实现”不太准确。 同外设交互基本都要通过驱动程序,系统会提供相应调用接口,在java中最终必然要靠native方法包装。但不一定是system call(核态),也可能是普通的API调用(用户态)。因为某些在操作系统上驱动程序工作在核态,某些操作系统上驱动程序工作在用户态。准确些的说法是“调用OS提供给用户的接口来实现”,你应该也是这个意思。 |
|
返回顶楼 | |
发表时间:2013-08-08
http://www.ibm.com/developerworks/cn/linux/l-async/
LZ 可以看下这个文章,里面有下面几张图,我觉得挺形象的, 还有unix网络编程里 也有说同步和阻塞模型 |
|
返回顶楼 | |