文档
Java NIO:浅析I/O模型 - Matrix海子 - 博客园
https://www.cnblogs.com/c3gen/p/6170504.html https://blog.csdn.net/weixin_39473824/article/details/80927404 https://blog.csdn.net/qq_22855325/article/details/72958345 Promise - 廖雪峰的官方网站 http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjava.html http://es6.ruanyifeng.com/#docs/promise
异步操作 - JavaScript 教程 - 网道 // 阮一峰 - 异步操作
Synchronous(同步) - 术语表 | MDN
异步 - 术语表 | MDN
前言 下面的内容都是自己参考资料,并结合自己的编程经验,学习思考得出的当下的认识,供参考交流,请辩证参考思考。
Js中多线程-单线程, 同步-异步,阻塞-非阻塞, 回调函数的关系理解 // 2018.12.12
提前说一句, 他们几者之间是有因果关系的.
1. 多线程/单线程
多线程: 程序同一时间可以做几件事.
单线程: 程序同一时间只能做一件事.
在JavaScript的世界中,所有代码都是单线程执行的.
JavaScript的单线程,与它的用途有关Python中的所有运算符号。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。
这决定了它只能是单线程,否则会带来很复杂的同步问题。
比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?「不明白」
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变.
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。
所以,这个新标准并没有改变JavaScript单线程的本质。
「但是一些不涉及到dom操作的操作, 如复杂的计算等, 可以考虑新开线程去做, 减少主线程占用时间, 且可以快速渲染结果.」
即通常情况下, Js中不必考虑多线程.
补充
Js单线程:
在浏览器的一个页面中,该页面的的Js只有一个master主线程[Js脚本运行在上面](注意:Js是单线程,但浏览器内部并不是单线程,I/O、定时器、事件监听等都是浏览器的其他线程完成的),所以叫单线程。
因为Js是单线程,所以程序的执行顺序都是从上到下依次进行的,同一时间内只能有一段代码被执行。
2. 同步/异步 先说一下,同步和异步本身的定义 然后,再看同步/异步的延伸。 同步
Synchronous(同步)
各方都实时(或者尽可能实时)地收取(而且必要的话也处理或者回复)信息的即时沟通方式,即为同步。
电话即为一个日常的例子:人们都倾向于在使用电话时即时地作出回应。
许多程序指令也是实时的:例如当输入一个算式时,除非编程人员有意为止,否则环境都会立即将结果反馈回来。
异步
Asynchronous(异步)
异步指两个或两个以上的对象或事件,不同时存在或发生(或多个相关事物的发生无需等待其前一事物的完成)。在计算机技术中,"异步"一词被用于两大语境。「这才是今天最解惑的地方」
网络与通信
异步通信是一种在双方或多方之间交换消息的方式。其中每个参与方,各自在他们方便或可操作的情况下接收并处理消息,而不是在收到消息后立即进行处理「针对服务端,异步处理」。另外,消息的发送无需等待确认信息「针对客户端的,异步请求」,前提是如果出现问题,接收方将请求更正或以其他方式处理该情况「这是考虑到异常处理」。
对人类来说,电子邮件就是一种异步通信方式;
发送者发送了一封邮件,接着接收者会在方便时读取和回复该邮件,而不是马上这样做。
双方可以继续随时发送和接收信息,而无需双方安排何时进行操作。
在软件进行异步通信时,一个程序可能会向另一软件(如服务器)请求信息,并在等待回复的同时继续执行其他操作。
例如,AJAX(Asynchronous JavaScript and XML) 编程技术(现在通常简写为"Ajax",不过现在的应用不常用 XML,而是用JSON)就是这样一种机制,它通过 HTTP 从服务器请求较少的数据,当结果可被返回时才返回结果,而不是立即返回。
软件设计
异步软件设计,通过构建代码扩展了异步的概念,按照这种设计编写的代码使得程序,能够要求一个任务与先前的一个(或多个)任务一起执行,而无需为了等待它们完成而停止执行。
当后来的任务完成时,程序将使用约定好的机制通知先前的任务,以便让它知道任务已经完成,以及如果有结果存在的话,这个结果是可用的。
还有许多用来实现异步软件的编程技术。查看文章Asynchronous JavaScript来了解它们吧。
同步/异步的延伸
同步/异步任务「软件设计,包含客户端和服务端,所有的程序,不过这里主要针对客户端,如,浏览器/Js」
同步/异步请求「网络与通信,针对客户端,如,浏览器 / JS」
同步/异步处理「网络与通信,针对服务端,如,Web Server / NodeJS」
同步/异步通信 = 同步/异步请求「客户端」 + 同步/异步处理「服务端」
不过有时候异步通信,通常可能认为是 异步请求,而不管是否异步处理。
其实严格起来,应该是,只有同步请求 + 同步处理,才是真正的同步,其他请求都是异步通信「也就是只要两端中有一端是异步操作,就是异步通信,同时也说明,同步和异步并没有优劣之分,都有各自适合的应用场景」
题外话:其实这世上很多的事情,都是双向选择的。
同步任务和异步任务
程序里面所有的任务「可以知道包含客户端和服务端, 不过这里主要说的是JS」,可以分成两类:同步任务(synchronous)和异步任务(asynchronous)。
同步任务是那些没有被引擎挂起、在主线程上排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。
异步任务是那些被引擎放在一边,不进入主线程、而进入任务队列的任务。
只有引擎认为某个异步任务可以执行了(比如 Ajax 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有“堵塞”效应。
举例来说,Ajax 操作可以当作同步任务处理,也可以当作异步任务处理,由开发者决定。
如果是同步任务,主线程就等着 Ajax 操作返回结果,再往下执行;
如果是异步任务,主线程在发出 Ajax 请求以后,就直接往下执行,等到 Ajax 操作有了结果,主线程再执行对应的回调函数。
补充
这里的同步/异步并不是其字面意思.
同步: 程序开始执行和执行结束的顺序和排队的顺序是一定是相同的。[因为是单线程, 就引出了队列的概念, 必须要排队,不管怎样, 就是排队, 一个一个来, 不允许插队]
也就是前一个程序执行完, 后一个程序才能执行, 否则就后者就一直处于等待过程中, 就引出了阻塞概念.
耗时过长的阻塞会拖延整个程序的执行。
常见的浏览器无响应(假死)
异步: 程序执行结束的顺序和排队的顺序是不相同的.
后者不等待前者执行完毕, 就可以开始执行.
异步编程的本质目的,就是为了提高CPU的执行时间「性能」「有时也是为了提供用户体验」
其实同步和异步,无论如何,做事情的时候都是只有一条流水线(单线程 [Js就是单线程的, 不会变]),
同步和异步的差别就在于这条流水线上,各个流程的执行顺序不同。
Js中,最基础的异步是setTimeout和setInterval函数,很常见,但是很少人有人知道其实这就是异步,因为它们可以控制Js的执行顺序.
3. 阻塞/非阻塞
如字面意思一样.
阻塞: 就是程序处于等待过程中, 必须等待当前程序执行完毕, 后面程序才能执行,对于要求速度和效率的web程序, 这明显是不可行的。
非阻塞: 程序不必等待前一个程序执行完, 就能执行, 这正是Js想达到的目的。
同步带来的问题, 就是如果程序中有耗时较长的操作, 就会造成阻塞。
所以Js中耗时操作, 如: JavaScript的所有网络操作,浏览器事件,都必须是异步执行。
异步操作, 就引出了下面的问题5(回调函数), 回调函数只是实现异步编程的几种方式之一。
常见的异步编程有:
回调函数, 事件监听, 发布/订阅, Promise对象 还有ES7发布的async与await
详情参考: Java异步编程的4种方法 - 阮一峰的网络日志
Promise: // 解决的只是异步编程风格的问题, 避免回调地狱.
简单介绍:
古人云:“君子一诺千金”,这种“承诺将来会执行”的对象在JavaScript中称为Promise对象。
Promise有各种开源实现,在ES6中被统一规范,由浏览器直接支持。
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
详细参见:
https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434
5008539155e93fc16046d4bb7854943814c4f9dc2000 // 廖雪峰
ES6 入门教程 // 阮一峰
补充:
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。
如果前一个任务耗时很长,后一个任务就不得不一直等着。于是就有一个概念,任务队列。
这里涉及到主线程和任务队列两个概念.暂不详述. // 任务队列又涉及到宏任务与微任务队列, 深入可以另行参考资料.
4. 回调函数
这是异步编程最基本的方法。
简单理解为:
类似于迂回战略, 先去做, 短时间内可能看不到响应, 但是一旦有响应, 就"立即"继续操作.
这里的立即, 不是绝对的立即, 也是要在主线程上的当前任务执行完毕之后, 才会执行,所以也是有延迟等待的[阻塞]。
5. 串行执行异步任务 / 并行执行异步任务 详情参见: 异步操作概述 - JavaScript 教程 - 网道 // 阮一峰 - 异步操作概述 属于流程控制. 串行执行异步任务: 按照顺序执行, 前后有依赖关系. 类似于接力赛. 比如,有若干个异步任务,需要先做任务1,如果成功后再做任务2,任何任务失败则不再继续, 并执行错误处理函数。 我们可以编写一个流程控制函数,让它来控制异步任务,一个任务完成以后,再执行另一个。 这就叫串行执行。 code
var items = [ 1, 2, 3, 4, 5, 6 ]; var results = [];
function async(arg, callback) { console.log('参数为 ' + arg +' , 1秒后返回结果'); setTimeout(function () { callback(arg * 2); }, 1000); }
function final(value) { console.log('完成: ', value); }
function series(item) { if(item) { async(item, function(result) { results.push(result); return series(items.shift()); }); } else { return final(results[results.length - 1]); } }
series(items.shift());
Note: 上面代码中,函数series 就是串行函数,它会依次执行异步任务,所有任务都完成后,才会执行final 函数。items 数组保存每一个异步任务的参数,results 数组保存每一个异步任务的运行结果。 注意,上面的写法需要6秒,才能完成整个脚本。执行的时候, 可以看到控制台每秒打印一个. 并行执行异步任务: 也是按照顺序执行[因为单线程], 但是任务之间是并列关系, 之间并无依赖关系. 类似于预装东西 试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的个人信息和好友列表,这两个任务是可以并行执行的. 有些时候,多个异步任务是为了容错。 比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可. 流程控制函数也可以是并行执行,即所有异步任务同时执行,等到全部完成以后,才执行final 函数。 code:
var items = [ 1, 2, 3, 4, 5, 6 ]; var results = [];
function async(arg, callback) { console.log('参数为 ' + arg +' , 1秒后返回结果'); setTimeout(function () { callback(arg * 2); }, 1000); }
function final(value) { console.log('完成: ', value); }
items.forEach(function(item) { async(item, function(result){ results.push(result); if(results.length === items.length) { final(results[results.length - 1]); } }) });
上面代码中,forEach 方法会同时发起六个异步任务,等到它们全部完成以后,才会执行final 函数。 相比而言,上面的写法只要一秒,就能完成整个脚本。 这就是说,并行执行的效率较高,比起串行执行一次只能执行一个任务,较为节约时间。但是问题在于如果并行的任务较多,很容易耗尽系统资源,拖慢运行速度。 因此有了第三种流程控制方式。 Note: 上面的写法需要1秒,便完成整个脚本。执行时, 可以看到控制台1秒打印完毕. 并行与串行的结合 所谓并行与串行的结合,就是设置一个门槛,每次最多只能并行执行n 个异步任务,这样就避免了过分占用系统资源。
var items = [ 1, 2, 3, 4, 5, 6 ]; var results = []; var running = 0; var limit = 2;
function async(arg, callback) { console.log('参数为 ' + arg +' , 1秒后返回结果'); setTimeout(function () { callback(arg * 2); }, 1000); }
function final(value) { console.log('完成: ', value); }
function launcher() { while(running < limit && items.length > 0) { var item = items.shift(); async(item, function(result) { results.push(result); running--; if(items.length > 0) { launcher(); } else if(running == 0) { final(results); } }); running++; } }
launcher();
上面代码中,最多只能同时运行两个异步任务。变量running 记录当前正在运行的任务数,只要低于门槛值,就再启动一个新的任务,如果等于0 ,就表示所有任务都执行完了,这时就执行final 函数。 Note: 这段代码需要3秒完成整个脚本,处在串行执行和并行执行之间。通过调节limit 变量,达到效率和资源的最佳平衡。 6. 总结
几者之间的关系如下:
最后个人的理解:
在Js实际开发中,并不会只有同步模式或者异步模式, 而尝尝是两者交叉使用或者说不存在完全异步编程的情况.
Question:
有没有可能出现只有异步的程序,这样的程序会出现什么问题?
Answer:
同步异步本身就是来区分程序的执行依赖,是在前面的执行结束后在执行 或者不关注前面程序的的执行结果。
但归根结底 先后顺序 或者说 同步 一定是存在的.
备注:
任何学习, 某种程度上都是参考并思考的过程, 所以建议:大胆参考, 认真思考, 小心求证.
|