js 多线程有几种实现方法,js多线程如何实现

大家好,小编来为大家解答以下问题,js 多线程有几种实现方法,js多线程如何实现,现在让我们一起来看看吧!

1.应用场景

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

2.学习/操作

文档

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:

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

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

备注:

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

3.问题/补充

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

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

理解思考记录:

2018-11-28  第一遍

2020-04-12  第二遍

2020-06-20  第三遍

4.参考

参见文档阅读列表

后续补充

...

1412593522-4.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值