JavaScript - 多线程/单线程, 同步/异步,阻塞/非阻塞, 回调函数

1.应用场景

学习弄清楚Js的一些重要概念,  对前端项目开发是极为重要的~~

JavaScript - 多线程/单线程, 同步/异步,阻塞/非阻塞, 回调函数_第1张图片

2.学习/操作

Js中多线程-单线程, 同步-异步,阻塞-非阻塞, 回调函数的关系理解  //2018.12.12

提前说一句, 他们几者之间是有因果关系的.

 

 

1.多线程/单线程

多线程: 程序同一时间可以做几件事.

单线程: 程序同一时间只能做一件事.

 

在JavaScript的世界中,所有代码都是单线程执行的.

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,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 操作从服务器得到了结果),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有“堵塞”效应。

 

举例来说,Ajax 操作可以当作同步任务处理,也可以当作异步任务处理,由开发者决定。

如果是同步任务,主线程就等着 Ajax 操作返回结果,再往下执行;

如果是异步任务,主线程在发出 Ajax 请求以后,就直接往下执行,等到 Ajax 操作有了结果,主线程再执行对应的回调函数。

 

补充:

这里的同步/异步并不是其字面意思.

 

同步: 程序开始执行和执行结束的顺序和排队的顺序是一定是相同的.[因为是单线程,就引出了队列的概念,必须要排队.不管怎样,就是排队,

一个一个来,不允许插队]

也就是前一个程序执行完, 后一个程序才能执行, 否则就后者就一直处于等待过程中, 就引出了阻塞概念.

耗时过长的阻塞会拖延整个程序的执行。常见的浏览器无响应(假死).

 

异步: 程序执行结束的顺序和排队的顺序是不相同的.

后者不等待前者执行完毕, 就可以开始执行.

异步编程的的本质目的就是为了提高cpu的执行时间.

 

其实同步和异步,无论如何,做事情的时候都是只有一条流水线(单线程 [Js就是单线程的,不会变]),

同步和异步的差别就在于这条流水线上各个流程的执行顺序不同。

 

Js中,最基础的异步是setTimeout和setInterval函数,很常见,但是很少人有人知道其实这就是异步,因为它们可以控制Js的执行顺序.

 

3.阻塞/非阻塞

如字面意思一样.

阻塞: 就是程序处于等待过程中, 必须等待当前程序执行完毕, 后面程序才能执行. 对于要求速度和效率的web程序,这明显是不可行的.

非阻塞: 程序不必等待前一个程序执行完, 就能执行, 这正是Js想达到的目的

 

同步带来的问题, 就是如果程序中有耗时较长的操作, 就会造成阻塞.

所以Js中耗时操作, 如:JavaScript的所有网络操作,浏览器事件,都必须是异步执行,

异步操作, 就引出了下面的问题5(回调函数), 回调函数只是实现异步编程的几种方式之一

 

常见的异步编程有: 

回调函数, 事件监听, 发布/订阅, Promise对象 还有ES7发布的async与await

详情参考: http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html

 

Promise: //解决的只是异步编程风格的问题, 避免回调地狱.

简单介绍:

古人云:“君子一诺千金”,这种“承诺将来会执行”的对象在JavaScript中称为Promise对象。

Promise有各种开源实现,在ES6中被统一规范,由浏览器直接支持。

 

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

 

详细参见:

https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434

5008539155e93fc16046d4bb7854943814c4f9dc2000  // 廖雪峰

http://es6.ruanyifeng.com/#docs/promise  //阮一峰

 

补充:

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。于是就有一个概念,任务队列。

 

这里涉及到主线程和任务队列两个概念.暂不详述. //任务队列又涉及到宏任务与微任务队列, 深入可以另行参考资料.

 

4.回调函数

这是异步编程最基本的方法。

简单理解为:

类似于迂回战略, 先去做, 短时间内可能看不到响应, 但是一旦有响应,  就"立即"继续操作. //这里的立即, 不是绝对的立即, 也是要在主线程上的当前任务执行完毕之后, 才会执行. 所以也是有延迟等待的[阻塞]

 

5.串行执行异步任务/并行执行异步任务 

详情参见: https://wangdoc.com/javascript/async/general.html  // 阮一峰 -  异步操作概述

属于流程控制.

 

串行执行异步任务: 按照顺序执行, 前后有依赖关系. 类似于接力赛.

比如,有若干个异步任务,需要先做任务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());

JavaScript - 多线程/单线程, 同步/异步,阻塞/非阻塞, 回调函数_第2张图片

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]);
    }
  })
});

JavaScript - 多线程/单线程, 同步/异步,阻塞/非阻塞, 回调函数_第3张图片

 

上面代码中,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();

JavaScript - 多线程/单线程, 同步/异步,阻塞/非阻塞, 回调函数_第4张图片

上面代码中,最多只能同时运行两个异步任务。变量running记录当前正在运行的任务数,只要低于门槛值,就再启动一个新的任务,如果等于0,就表示所有任务都执行完了,这时就执行final函数。

 

Note:

这段代码需要3秒完成整个脚本,处在串行执行和并行执行之间。通过调节limit变量,达到效率和资源的最佳平衡。

 

 

6. 总结

几者之间的关系如下:

JavaScript - 多线程/单线程, 同步/异步,阻塞/非阻塞, 回调函数_第5张图片

 

最后个人的理解:

在Js实际开发中,并不会只有同步模式或者异步模式, 而尝尝是两者交叉使用或者说不存在完全异步编程的情况.

 

Question:

有没有可能出现只有异步的程序,这样的程序会出现什么问题?

Answer:

同步异步本身就是来区分程序的执行依赖,是在前面的执行结束后在执行 或者不关注前面程序的的执行结果。

但归根结底 先后顺序 或者说 同步 一定是存在的.

 

备注:

任何学习, 某种程度上都是参考并思考的过程, 所以建议:大胆参考, 认真思考, 小心求证.

3.问题/补充

1.理解还是有些不清晰.

暂时的解决办法是,多实践前端开发, 代码写的多了, 理解自然就会多一些.  20200412 公寓

 

理解思考记录:

2018-11-28  第一遍

2020-04-12  第二遍

2020-06-20  第三遍

4.参考

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
https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014345008539155e93fc

16046d4bb7854943814c4f9dc2000
http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html
http://es6.ruanyifeng.com/#docs/promise

https://wangdoc.com/javascript/async/index.html  //阮一峰 - 异步操作

后续补充

...

 

JavaScript - 多线程/单线程, 同步/异步,阻塞/非阻塞, 回调函数_第6张图片

 

你可能感兴趣的:(前端-FRONT,END)