可读性:★★★★✰ 理解难度:★★★★✰
概述
对象是过程的抽象,线程是调度的抽象。
一、为什么要并发
并发是一种解耦策略,它帮助我们把做什么(目的)和何时做(时机)分解开。这样的策略能明显改进应用的吞吐量和结构。
二、并发编程三要素
要实现并发编程,必须保证以下3个要素,不然程序在并发的过程中就会存在不可预期的bug。
2.1 原子性
原子,一个不可再被分割的颗粒。原子性,指一个或多个不可分割的操作。
比如一个参数的读写操作必须是一个原子操作,不可以同时进行修改和读取操作。
我们可以用synchronized 或 Lock 来把读写操作“变成”原子操作。 简单来说,就是一个线程对变量修改时,其它线程暂时不能访问该变量。
2.2 可见性
当多个线程访问同一个变量时,其中一个线程对此变量作了修改,其他线程可以立刻读取到最新修改后的值。
int a = 0
//线程A执行的代码
a = 10
//线程B执行的代码
j = a
2.3 有序性
程序的执行顺序按照代码的先后顺序来执行。
// 线程 1
init()
initDone = true
// 线程2
while(initDone){
executeAfterInit()
}
上面这段代码,如果按书写顺序执行,则不会有什么问题。
但是,如果线程1的initDone = true
,在init()
之前执行了。则会导致线程2的executeAfterInit
方法出现不可预期的问题。
三、实现并发编程的方式
很多编程语言都有实现并发编程的方式,比如java:
- 继承Thread类
- 实现Runnable接口
- 通过实现Callable接口
- 通过线程池来实现多线程
- 通过Timer定时器来实现多线程
- jdk1.8通过stream实现多线程
四、Javascript 中的并发编程
这里进行一下概念的转换,并发是多个线程同时执行行,而不阻塞主线程。其实与Js中的异步任务很相似。那么前端有哪些实现异步的方式呢?
4.1 回调函数
const callback = () => {
// do something
}
// 发起ajax请求,返回后执行回调
ajax(url, callback)
其中包括:事件监听、发布订阅等。
4.2 Promise
Promise规范有很多,包括Javascript’s Promises/A/B/D/A+,es6采用了Promises/A+的规范。
Promise
对象有以下两个特点。
(1)Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。状态的改变是不可逆。
这里有一篇深入理解Promise的文章:从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节
4.3 Generator
generator即生成器,是ES6规范带来的新内容,在generator能够让我们在函数执行时任意地方暂停,在后续遇到合适的时机需要使用这个函数时继续执行。以往我们遇到的函数都是一口气执行到底,而generator的特点就是让函数执行到中间“刹车”,在需要它的时候接着执行。Generator运行是惰性的。(1)
function* foo() {
yield 'result1'
yield 'result2'
yield 'result3'
}
const gen = foo()
console.log(gen.next()) // {value: "result1", done: false}
console.log(gen.next()) // {value: "result2", done: false}
console.log(gen.next()) // {value: "result3", done: false}
console.log(gen.next()) // {value: undefined, done: true}
Generator的作用
-
实现迭代器
ES 标准虽然没有直接支持 Iterator API,但它通过了内建迭代器的方案。就是使用 Symbol.iterator,这是 ES6 引入的。
实现了
内建迭代器
方法的对象称为可迭代对象,String
,Array
,TypedArray
,Map
和Set
是 Javascript 中内置的可迭代对象:const a = [1, 3, 5] const iter = a[Symbol.iterator]() // Array Iterator {} iter.next() // {value: 1, done: false}
对象的扩展运算符(...)内部其实是调用了 Iterator 接口。
-
Generator是协程在js上的实现。通过generator,我们可以在单线程的JavaScript里使用协程
-
Generator可以用来模拟多线程的休眠机制
-
generator本身作为异步编程的解决方案,可以用来解决异步任务,可配合co库使用
迭代器模式
迭代器模式(Iterator Pattern)又称为游标模式(Cursor Pattern),它提供一种顺序访问集合/容器对象元素的方法,而又无须暴露集合内部表示。迭代器模式可以为不同的容器提供一致的遍历行为,而不用关心容器内容元素组成结构,属于行为型模式。
应用:迭代器模式在我们生活中应用的也比较广泛,比如物流系统中的传送带,不管传送的是什么物品,都被打包成一个一个的箱子并且有一个统一的二维码。这样我们不需要关心箱子里面是啥,我们在分发时只需要一个一个检查发送的目的地即可。再比如,我们平时乘坐交通工具,都是统一刷卡或者刷脸进站,而不需要关心是男性还是女性,是残疾人还是正常人等个性化的信息。
4.4 async/await
async
函数是 Generator
函数的语法糖。await
意思是async await(异步等待)。这个关键字只能在使用async
定义的函数里面使用。
async/await 让异步代码看起来、表现起来像同步代码,更加易于理解与阅读。
async函数对 Generator 函数的改进:
- 内置执行器,不需要调用
next()
方法 - 更好的语义
- 更广的适用性,不需要co模块配合,await命令后面,可以是 Promise 对象和原始类型的值
- 返回 Promise
4.5 Web workers
浏览器的渲染进程是多线程的,它包括如下线程:
- GUI渲染线程
- JS引擎线程,GUI渲染线程与JS引擎线程是互斥的
- 事件触发线程
- 定时触发器线程
- 异步http请求线程
由于,GUI渲染线程与JS引擎线程是互斥的,Web Worker 可以将一些耗时的数据处理操作从主线程中剥离,使主线程更加专注于页面渲染和交互。
Web Workers应用场景
- 懒加载
- 文本分析
- 流媒体数据处理
- canvas 图形绘制
- 图像处理
- ...
Web Workers的分类
-
Dedicated Workers 专用线程
专用线程仅能被创建它的脚本所使用(一个专用线程对应一个主线程)
-
Shared Workers 共享线程
共享线程能够在不同的脚本中使用(一个共享线程对应多个主线程)
-
Service Workers 服务线程
专用线程示例:
// main.js
var worker = new Worker('task.js');
worker.onmessage = e => {
console.log('worker.onmessage')
this.regionalizationData = e.data
this.doSmthing()
}
// task.js
const data = [1,2,3]
postMessage(data)
本文参考《代码整洁之道》(Robert C. Martin著,韩磊译)。
浙江大华技术股份有限公司-软研-智慧城市产品研发部招聘高级前端,欢迎来撩,有意向可发送简历到chen_zhen@dahuatech.com,长期有效
上一篇:十二、迭进
下一篇:十四、逐步改进