线程 vs 进程、并行 vs 并发
在一个程序当中,可以有多个进程。在一个进程里,也可以有多个线程。内存在进程间是不共享的,而在线程里,是可以共享的。通过下图,你可以有更直观的理解。
并发是同一时间段进行的任务,而并行是两个在同一时刻进行的任务。除非是多核 CPU,否则如果只有一个 CPU 的话,多线程本身并不支持并行,而只能通过线程间的切换执行来做到并发。从下图中,你可以对并发和并行有更直观的认识。
前后端语言如何支持线程并行开发
如果单从语言的层面来看,JavaScript 本身没有线程的规范,也没有内置的对外暴露给开发者用来创建线程的接口。所以 JavaScript 本身的执行是单线程的。
iOS、安卓和 unity 的开发中,开发者分别有 GradeCentralDispatch、WorkManager 和 Job System 这些支持并行开发的强大工具包。之所以这些语言不仅支持多线程,还助力并行的开发。是因为这些语言所编写的程序都和前端用户有深度的交互。
和前端不同,在很多的后端高级语言,如 Ruby 或 Python 中,无论 CPU 是单核还是多核,线程都是被全局解释器锁(GIL,global interpreter lock)限制并行的,而只支持并发。
JavaScript 中的多线程并行
开发从上面的例子中,我们可以看到,前端语言对线程和并行的支持,总体要优于后端。
JavaScript 虽然自带函数式编程的支持,而且在这几年加强了响应式编程的设计思想,但是在多线程和并行开发方面的能力还是有缺失的,但是这并不代表多线程在前端完全不能实现。
JavaScript 中的异步
因为近些年中,JavaScript 中大多数的所谓“多任务处理”,都是通过将任务拆分成任务单元后异步执行的。这些不同的任务可以被看做是某种“并发”。
这里的任务拆分,就是通过我们上节讲过的事件 callback 回调和 promise,将任务分成异步的任务单元。在异步的场景中,同一时间只有一个调用栈。
事件 callback 回调、promise、await、用户事件和 timeout 都是采用任务拆分的原理。
也就是说只有一个 JavaScript 指令结束后,下一个指令才能执行,所以异步也是有成本的,说响应和渐进式仍然是在串行的基础上操作,而不是在并行的基础上实现的。
JavaScript 中用 Web Worker 支持并行
WebWorker。它可以打破帧同步,允许我们在和主线程并行的工作线程上执行其它的指令。
*我们知道的 Chrome、Safari 和 FireFox 等浏览器都有自己的虚机来实现 JavaScript。这和我们在前端用到的文件系统、网络、setTimeout、设备等功能一样,都是由嵌入环境的 Node 或浏览器虚机提供,而不是语言本身提供的。所以多线程的接口也是一样,也是由浏览器提供的。而浏览器或虚机提供的支持多线程的 API 就是 Web Worker。
要创建一个 Web Worker 非常简单,我们只需要类似下面的一个 new 语句。之后,如果可以通过 postMessage 在 main.js 和 worker.js 之间传递信息,双方也可以通过 onMessage 来接收来自对方的消息。*
// main.js
var worker = new Worker('worker.js');
worker.postMessage('Hello world');
worker.onmessage = (msg) => {
console.log('message from worker', msg.data);
}
// worker.js
self.onmessage = (msg) => {
postMessage('message sent from worker');
console.log('message from main', msg.data);
}
在 JavaScript 中,有几种不同的工作线程,分别为 dedicated worker、shared worker 和 service worker。我们可以分别来看看它们的作用。这里我们先来看看 dedicated worker。
dedicated worker 只在一个 realm 中可以使用。它也可以有很多的层级,但是如果层级过多,可能会引起逻辑的混乱,所以在使用上还是要注意。
和 dedicated worker 相对的是 shared worker,顾名思义,如果说 dedicated 是专属的,那么 shared 则是可共享的,所以 shared worker 可以被不同的标签页、iframe 和 worker 访问。但 shared worker 也不是没有限制,它的限制是只能被在同源上跑的 JavaScript 访问。
service worker,它既然叫做服务,那就和后端有关。它的特点就是在前端的页面都关闭的情况下,也可以运行。
信息传递模式
结构化拷贝算法
通过先拷贝再传递的方式。这里使用到的一个拷贝算法就是类似我们之前说到的深拷贝,也叫做结构化拷贝(structured clone)。
请求和反馈模式
当我们要传递更复杂的数据结构时,比如如果我们需要传递的是下面这样一个带有参数的函数调用,在这个时候,我们需要先将函数调用转化为一个序列,也就是一个对应的是我们的本地调用远程过程调用,叫做 PRC(Remote Procedure Call)。
基于 postMessage 异步的特性,它返回的不是一个结果,而是一个 await 的 promise。
isOdd(3);
is_odd|num:3
worker.postMessage('is_odd|num:3');
worker.postMessage('is_odd|num:5');
worker.onmessage = (result) => {
// 'true'
// 'false'
};
结构化拷贝算法支持除了 Symbol 以外的其它类型的原始数据,这里包含了布尔、null、undefined、数字、BigInt 和我们用到的字符串。结构化拷贝算法也可以支持多种的数据结构,包括数组、字典和集合等。但是函数(function)和(class)类是不能通过 postMessage 来传递的。
命令和派发模式
通过开发者自己实现的一个来保证指令派发的逻辑就叫做 command dispatcher 模式。
var commands = {
isOdd(num) { /*...*/ },
isEven(num) { /*...*/ }
};
function dispatch(method, args) {
if (commands.hasOwnProperty(method)) {
return commands[method](...args);
}
//...
}
此文章为2月Day7学习笔记,内容来源于极客时间《Jvascript进阶实战课》,大家共同进步💪💪
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。