国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

Javascript 多線程編程的前世今生

開發(fā) 開發(fā)工具
Javascript 不是單線程的嗎?Javascript IO 阻塞和其他異步的需求(例如 setTimeout, Promise, requestAnimationFrame, queueMicrotask 等)不是通過事件循環(huán)(Event Loop)來解決的嗎?

 作者:jolamjiang,騰訊 WXG 前端開發(fā)工程師

一篇關(guān)于 Web Worker、SharedArrayBuffer、Atomics 的文章。

[[330324]]

為什么要多線程編程

大家看到文章的標題《Javascript 多線程編程》可能立馬會產(chǎn)生疑問:Javascript 不是單線程的嗎?Javascript IO 阻塞和其他異步的需求(例如 setTimeout, Promise, requestAnimationFrame, queueMicrotask 等)不是通過事件循環(huán)(Event Loop)來解決的嗎?

沒有錯,Javascript 的確是單線程的,阻塞和其他異步的需求的確是通過實現(xiàn)循環(huán)來解決的,但是這套機制當線程需要處理大規(guī)模的計算的時候就不大適用了,試想一下一下的場景:

  1. 你需要實現(xiàn)對文件的加解密。
  2. 你的 VirtualDom 樹有很多元素(例如上萬個),你需要對這棵樹進行 Diff 操作。
  3. 你需要在瀏覽器“挖礦”。

上面這些場景都會阻塞主線程,也就是當進行這些操作的時候,你的頁面是卡住的,設(shè)置當頁面卡住一段時間之后,Chrome 等瀏覽器或者操作系統(tǒng)會建議你 Kill 掉整個 Tab 或者進程。這顯然不是我們想看到的事情。正因為這些場景的存在,瀏覽器提出了 W3C 在 2013 年提出了 Web Worker 草案,這個草案的提出就是為了解決上述這些問題。

為了讓大家感受 JS 多線程能夠干什么,筆者寫了一個基于 Web Worker(線程)、ShareArrayBuffer(共享內(nèi)存)、Atomics(鎖)等 Web API 的在前端壓縮和解壓文件(基于 DEFLATE 算法)的 demo:

Web Worker

Chrome 瀏覽器中每個 Tab 都是一個進程,每個進程都會有一個主線程,網(wǎng)頁的渲染(Style, Layout, Paint, Composite)會在主線程進行操作。主線程可以發(fā)起多個 Web Worker,Web Worker 對應(yīng)“線程”的概念。

 

每個 Web Worker 都對應(yīng)一個腳本文件,主線程可以通過像以下的代碼去發(fā)起多個 Web Worker,并且通過基于事件的 API 與 Web Worker 通信:

main.js

  1. let worker = new Worker("work.js"); 
  2. worker.postMessage("Hello World"); 
  3. worker.onmessage = function (event) { 
  4.   console.log("Received message " + event.data); 

Web Worker 也通過相應(yīng)的實現(xiàn) API 與主線程進行通信

worker.js

  1. this.addEventListener("message"function (e) { 
  2.   this.postMessage("You said: " + e.data); 
  3. }, false); 

Web Worker 通訊的效率與同步問題

主線程與 Web Worker 通過 postMessage(data: any) 通信的時候,data 會先被 copy 一份再傳給 Web Worker;同樣地,當 Web Worker 通過 postMessage(data: any) 與主線程通信的時候,data 也會同樣先被 copy 一份再傳給主線程。

 

這樣做顯然會導(dǎo)致通信上的效率問題,試想一下你需要在 Web Worker 里面解壓一個 1G 大小的問題,你需要把整個 1G 的文件 copy 到 Web Worker 里,Web Worker 解壓完這個 1G 文件后,再把解壓完的文件 copy 回主線程里。

SharedArrayBuffer

為了解決通訊效率問題,瀏覽器提出了 ShareArrayBuffer,ShareArrayBuffer 基于 ArrayBuffer 和 TypedArray API。ArrayBuffer 對應(yīng)一段內(nèi)存(二進制內(nèi)容),為了操作這段內(nèi)存,瀏覽器需要提供一些視圖(Int8Array 等),例如可以把這段內(nèi)存當做每 8 位一個單元的 byte 數(shù)組,每 16 位一個單元的 16 位有符號數(shù)數(shù)組。

 

注意:ArrayBuffer 中的二進制流被翻譯成各種視圖的時候采用小端還是大端是由具體硬件決定的,絕大部分情況下是采用小端字節(jié)順序。

這段內(nèi)存可以在不同的 Worker 之間共享,但是內(nèi)存的共享又會產(chǎn)生另外的問題,也就是競爭的問題(race onditions):

計算機指令對內(nèi)存操作進行運算的時候,我們可以看做分兩步:一是從內(nèi)存中取值,二是運算并給某段內(nèi)存賦值。當我們有兩個線程對同一個內(nèi)存地址進行 +1 操作的時候,假設(shè)線程是先后順序運行的,為了簡化模型,我們可以如下圖表示:

 

上面兩個線程的運行結(jié)果也符合我們的預(yù)期,也即線程分別都對同一地址進行了 +1 操作,最后得到結(jié)果 3。但因為兩個線程是同時運行的,往往會發(fā)生下圖所表示的問題,也即讀取與寫入可能不在一個事務(wù)中發(fā)生:

 

這種情況就叫做競爭問題(Race Condition)。

Atomics

為了解決上述的競爭問題,瀏覽器提供了 Atomics API,這組 API 是一組原子操作,可以將讀取和寫入綁定起來,例如下圖中的 S1 到 S3 操作就被瀏覽器封裝成 Atomics.add() 這個 API,從而解決競爭問題。

 

Atomics API 具體包含:

  • Atomics.add()
  • Atomics.and()
  • Atomics.compareExchange()
  • Atomics.exchange()
  • Atomics.isLockFree()
  • Atomics.load()
  • Atomics.notify()
  • Atomics.or()
  • Atomics.store()
  • Atomics.sub()
  • Atomics.wait()
  • Atomics.xor()

有了這套 API,我們可以實現(xiàn)像 Golang 中的 Golang Synchronization Primitives 的功能。Mutex 和 Cond 的實現(xiàn)會在下面介紹。

WebAssembly

有了 SharedArrayBuffer 和 Atomics 能力之后,證明瀏覽器能夠提供內(nèi)存共享和鎖的實現(xiàn)了,也就是說 WebAssembly 線程在瀏覽器機制上能夠高效地得到保證。

其實我嚴重懷疑 SharedArrayBuffer 和 Atomics 是為了支持 WebAssembly 才把 API 順便提供給 JS Runtime 的,因為目前為止沒有看到 ES 有比較豐富的關(guān)于鎖的草案(例如像 Java 中的 synchronized 關(guān)鍵字)。

Mutext 和 Cond 的實現(xiàn)

上面提到了,基于 ShareArrayBuffer 和 Atomics 可以開發(fā)像 Golang Synchronization Primitives 一樣的 API,下面介紹一下 Mutex 和 Cond 的實現(xiàn)。實現(xiàn)的介紹是基于 Mozzila Javascript 編譯器工程師 Lars T Hansen 實現(xiàn)關(guān)于鎖的庫。

Mutex

首先說一下 Mutex 的功能,Mutex 的 API 大概是這樣的:

  1. let mutex = new Lock(shareArrayBuffer, ...); 
  2. mutex.lock(); 
  3. doSomething(); 
  4. mutex.unlock(); 

Mutex 可以保證 lock() 和 unlock() 之間的代碼代碼不會被打斷。下面是介紹具體實現(xiàn):

首先定義 Mutex 的三個狀態(tài)以及對應(yīng)的狀態(tài)機

  • UNLOCK: 未鎖定
  • LOCKED: 被鎖定
  • WAITED: 被鎖定且大于等于 1 個線程在等待該鎖

 

對于 Worker 線程來說 Mutex 的每個狀態(tài)都可能是初始態(tài),狀態(tài)與狀態(tài)間扭轉(zhuǎn)會產(chǎn)生一些操作且進入下一狀態(tài):

加鎖 lock()

  • 初始狀態(tài)為UNLOCK: 鎖未被搶占,將狀態(tài)扭轉(zhuǎn)為 LOCKED,線程進行后續(xù)操作。
  • 初始狀態(tài)為LOCKED: 鎖已被搶占,將狀態(tài)扭轉(zhuǎn)為 WAITED,并將線程設(shè)置為等待態(tài),并將線程設(shè)置為當鎖的狀態(tài)不為 WAITED 的時候可能被喚醒,一旦被喚醒則該線程擁有鎖,線程進行后續(xù)操作。
  • 初始狀態(tài)為WAITED: 鎖已被搶占,并將線程設(shè)置為等待態(tài),并將線程設(shè)置為當鎖的狀態(tài)不為 WAITED 的時候可能被喚醒,一旦被喚醒則該線程擁有鎖,線程進行后續(xù)操作。

釋放 unlock()

1.初始狀態(tài)為LOCKED: 鎖被搶占且未被等待,將狀態(tài)扭轉(zhuǎn)為 UNLOCK,線程進行后續(xù)操作。

2.初始狀態(tài)為WAITED: 鎖被搶占且被等待,將狀態(tài)扭轉(zhuǎn)為 LOCKED,喚醒一個在等待態(tài)的線程,線程進行后續(xù)操作。

上面描述的邏輯的對應(yīng)的代碼如下:

  1. // lock 
  2. Lock.prototype.lock = function () { 
  3.   const iab = this._iab; 
  4.   const stateIdx = this._ibase; 
  5.   let c; 
  6.   if ((c = Atomics.compareExchange(iab, stateIdx, 0, 1)) != 0) { 
  7.     do { 
  8.       if (c == 2 || Atomics.compareExchange(iab, stateIdx, 1, 2) != 0) 
  9.         Atomics.wait(iab, stateIdx, 2); 
  10.     } while ((c = Atomics.compareExchange(iab, stateIdx, 0, 2)) != 0); 
  11.   } 
  12.  
  13. // unlock 
  14. Lock.prototype.unlock = function () { 
  15.     const iab = this._iab; 
  16.     const stateIdx = this._ibase; 
  17.     let v0 = Atomics.sub(iab, stateIdx, 1); 
  18.     // Wake up a waiter if there are any 
  19.     if (v0 != 1) { 
  20.         Atomics.store(iab, stateIdx, 0); 
  21.         Atomics.notify(iab, stateIdx, 1); 
  22.     } 

可以看到鎖的實現(xiàn)用到了 Atomics.compareExchange() 和 Atomics.wait()(相當于 Linux 中的 futex)兩個原子操作。

Cond

Cond 是基于 Mutex 實現(xiàn)的,它的大致功能是持有鎖的情況下可進行兩種操作:

  • wait(): 本線程進度進入等待態(tài),并且被喚醒的時候重新持有鎖。
  • notifyOne(): 喚醒一個正在等待態(tài)的線程。

具體使用方法如下:

  1. // thread A 
  2. var msg = new Int32Array(sab, msgLoc, 1); 
  3. lock.lock(); 
  4. while (msg[0] < numWorkers) 
  5.  cond.wait(); 
  6. lock.unlock(); 
  7.  
  8. // thread B, C, D, E, … 
  9. var msg = new Int32Array(sab, msgLoc, 1); 
  10. lock.lock(); 
  11. msg[0]++; 
  12. cond.notifyOne(); 
  13. lock.unlock(); 

由于 Cond 是基于 Mutex,前置條件是持有鎖,后置條件是釋放鎖,你可以看做 Cond 只有兩個狀態(tài):

  • NORMAL: 非等待態(tài),調(diào)用 wait() 轉(zhuǎn)化為 WAITED 狀態(tài),并把線程設(shè)置為等待態(tài),并且被喚醒的時候重新持有鎖,然后進行后續(xù)操作。
  • WAITED: 等待態(tài)(不對應(yīng)上述 Lock 的 WAITED 態(tài)),調(diào)用 notifyOne() 將狀態(tài)設(shè)置為 NORMAL 態(tài),重新喚醒一個處于等待態(tài)的線程,然后進行后續(xù)操作。

 

異步鎖

上述介紹的鎖都是同步的,Atomics.wait 不能在主線程使用,在主線程使用的話瀏覽器會拋出異常:

  1. Uncaught TypeError: Atomics.wait cannot be called in this context 

所以我們需要設(shè)計所謂的”異步鎖“,所謂的異步鎖原理很簡單,就是將同步鎖里面的 Atomics.wait() 操作交給一個新的線程,主線程和這個線程通過事件通信來異步化這里的操作。具體實現(xiàn)可以參照這個文件)。

demo 實現(xiàn)

介紹完上述的知識之后,就可以用相關(guān)的 API 就可以實現(xiàn)我們的 demo 了,首先畫一下我們 demo 的架構(gòu)圖:

 

如圖所示,在線解壓縮這個 demo 主要分為兩個線程:

主線程:負責(zé)調(diào)用 Dom API 等,主要負責(zé) UI 更新。

工作線程:負責(zé)文件的壓縮/解壓。

兩個線程間的通信是通過讀寫兩段共享內(nèi)存來實現(xiàn)的,對于共享內(nèi)存的訪問,通過鎖來解決競爭問題。需要注意的是,主線程的寫緩存也即工作線程的讀緩存,反之亦然。

demo 的具體實現(xiàn)可以參照 demo 的 Github 地址。

目前多線程編程的不足

目前只通過瀏覽器提供的 API 來進多線程開發(fā)的話成本非常大,主要有兩方面問題:

過于底層的 API

1.需要你實現(xiàn)語言級、或者系統(tǒng)級的 lock API,參照 Golang 的 lock API。

2.沒有語法上的支持,例如 Java synchronized 關(guān)鍵字等。

普通的 Javascript Object 無法共享

這其實也是 API 過于底層的另一方面的體現(xiàn),也就是說對 JS 對象進行內(nèi)存共享的話,你需要開辟一段 SharedArrayBuffer,然后在此之上實現(xiàn)對 JS 對象的序列化、反序列化、更新等操作,實現(xiàn)成本也是比較大的。

事實上我們也不應(yīng)該輕易手動實現(xiàn)相關(guān)的庫或者功能,因為相關(guān)領(lǐng)域的問題非常復(fù)雜、需要仔細的設(shè)計和實現(xiàn)。例如我們可以先使用下面這兩個庫:

  1. parlib-simple: 這個庫里面有類似于 Golang 里面 channel 一樣的 API。
  2. js-lock-and-condition: 這里庫有 Mutex 和 Cond 實現(xiàn)。

總結(jié)

瀏覽器提供給了我們進行多線程的能力,例如 PWA 或者 WebAseembly 與 JS 混用等場景都會用到上述的機制,如果你想實現(xiàn)一個高性能的網(wǎng)頁客戶端程序(例如 Figma 一樣的殺手級應(yīng)用),你最好也用上上述的機制。值得注意的是,用了鎖可能會降低你的程序的性能,具體要看線程切換和等待是的成本是否能夠抵消內(nèi)存拷貝的成本,例如 demo 完全可以改成無鎖的,代價將文件內(nèi)容拷貝到共享線程,并把工作線程的內(nèi)容拷貝回主線程。

雖然上面建議不要輕易實現(xiàn)自己的庫,例如上面的 lock 代碼短短幾行,但是其中的推導(dǎo)可以足夠?qū)懯畮醉摰?Paper 了,但是這里的基礎(chǔ)能力很匱乏,據(jù)筆者了解,TC39 提案中鮮少出現(xiàn)關(guān)于多線程編程的提案,目前僅發(fā)現(xiàn)以下這個:

  1. proposal-atomics-wait-async 

但是,如果自信有能力和時間建設(shè)這些基礎(chǔ)能力的話,這個領(lǐng)域的確是“廣闊天地,大有作為”,特別是如果你的項目準備用 WebAseembly 和 JS 混用的情況(例如 Figma 就是用了 WebAssembly 和 React)。

 

責(zé)任編輯:武曉燕 來源: 51CTO
相關(guān)推薦

2019-08-05 10:08:25

軟件操作系統(tǒng)程序員

2011-08-23 09:52:31

CSS

2014-07-30 10:55:27

2025-02-12 11:25:39

2015-11-18 14:14:11

OPNFVNFV

2021-05-31 20:06:57

網(wǎng)元協(xié)議網(wǎng)關(guān)

2014-07-15 10:31:07

asyncawait

2013-05-23 16:23:42

Windows Azu微軟公有云

2014-07-21 12:57:25

諾基亞微軟裁員

2019-06-04 09:00:07

Jenkins X開源開發(fā)人員

2021-06-17 07:08:19

Tapablewebpack JavaScript

2016-12-29 18:21:01

2016-12-29 13:34:04

阿爾法狗圍棋計算機

2012-05-18 16:54:21

FedoraFedora 17

2016-11-08 19:19:06

2016-11-03 13:33:31

2013-11-14 16:03:23

Android設(shè)計Android Des

2015-06-11 11:10:09

對象存儲云存儲

2022-11-07 14:23:35

RPA人工智能流程自動化管理

2019-04-28 09:34:06

點贊
收藏

51CTO技術(shù)棧公眾號

亚洲精品无播放器在线播放| 精品99一区二区| 九七伦理97伦理手机| 一区二区三区国产豹纹内裤在线| 很黄很污的网站| 亚洲成人免费在线观看| 黄色片在线播放| 日韩欧美123| 97精品国产99久久久久久免费| 久久这里只有精品99| 精品无人区一区二区| 国产又爽又黄的激情精品视频| 激情另类综合| 伊人久久99| 91久色porny| 在线免费福利| 亚洲国产精品一区二区久| 国产中文欧美日韩在线| 国产日韩欧美在线视频观看| 日日夜夜精品免费视频| 情侣黄网站免费看| 五月婷婷欧美视频| 国产色婷婷在线| 久久久亚洲精选| 亚洲高清自拍| 欧美xxxxx在线视频| 精品美女久久久久久免费| 欧美xxxx做受欧美88bbw| 毛片精品免费在线观看| 小小影院久久| 男人c女人视频| 亚洲成人午夜电影| 午夜伦理福利在线| 国产精品久久久久久搜索 | 欧美激情一区二区三区成人 | 亚洲成av人综合在线观看| 午夜成年人在线免费视频| 久久99久久99精品中文字幕| 国产精品v亚洲精品v日韩精品| 欧洲xxxxx| 亚洲国产一区视频| 香蕉久久免费电影| 亚洲永久免费观看| 91免费视频观看| 中国日本在线视频中文字幕| 欧美精品在线极品| 亚洲女人av| www.aqdy爱情电影网| 精品亚洲一区二区三区在线观看| 欧美日韩国产传媒| 亚洲精品无码国产| 欧美日韩电影在线播放| 好吊妞视频这里有精品 | 国产精品盗摄一区二区三区| 性直播体位视频在线观看| 91精品国产高清| 国内精品国产成人| av电影在线观看网址| 91精品国产高清| 国产成人午夜精品5599| 蜜芽在线免费观看| 国产精品久久久久99| 92精品国产成人观看免费| 欧美男男video| 99精品在线直播| 亚洲精品日韩一| 日韩精品成人| 少妇一晚三次一区二区三区| 在线不卡欧美精品一区二区三区| 欧美男人操女人视频| 国产va亚洲va在线va| 日韩精品一区二区三区老鸭窝| 欧美黄色录像片| 成年人免费看的视频| 久久亚洲私人国产精品va| 日韩精品电影一区亚洲| 免费福利在线观看| 国产精品高潮视频| 中文字幕一区二区不卡| 91视频成人| 色欲色香天天天综合网www| 精品国产麻豆免费人成网站| 中文字幕日韩欧美精品高清在线| 台湾十八成人网| 久热在线中文字幕色999舞| 另类人妖一区二区av| 免费黄色在线观看| av日韩中文字幕| 精品福利在线视频| 99久久夜色精品国产亚洲96| 男女爱爱免费网站| 91国内揄拍国内精品对白| 国产欧美精品一区aⅴ影院 | 无吗不卡中文字幕| 国产麻豆一区二区三区精品视频| 久久这里只精品| 欧美激情综合色| 国产精品高潮久久久久无| 一本色道69色精品综合久久| 国产理论在线播放| 97精品视频在线| 亚洲激情在线激情| 日韩美女毛片| 狠狠干在线视频| 99久久99久久精品国产片| 精品欧美激情精品一区| 欧美日韩一区二区高清| 日本在线免费中文字幕| 精品国产日本| 日韩欧美视频一区| 精东粉嫩av免费一区二区三区| 性国裸体高清亚洲| 国产无限制自拍| 久久久噜噜噜久噜久久| 亚洲日韩欧美一区二区在线| 日韩av有码| 日本精品一区二区三区在线播放| 视频一区二区三区免费观看| 亚洲欧美激情视频| 久久嫩草精品久久久精品 | 热门国产精品亚洲第一区在线| 国产精品不卡一区二区三区| 国产成人ay| 四虎影院在线域名免费观看| 精品一区二区久久久久久久网站| 亚洲精品一区二区三区四区高清| 国产精品18久久久久久久网站| 精品国产不卡一区二区| http://嫩草影院| 成人在线观看91| 亚洲精品国产拍免费91在线| 91在线云播放| 日本大胆欧美| √天堂8在线网| 日本a视频在线观看| 88xx成人精品| 精品视频色一区| 岛国精品一区二区| 国产精品密蕾丝视频下载 | 自拍偷拍精选| 5566av亚洲| 日韩精品免费在线视频| 久久精品夜色噜噜亚洲a∨| 精品国产欧美日韩| 国产精品久久久久久福利| 国产精品久久久久久久久电影网| 欧美亚洲视频在线看网址| 欧美综合色免费| 国产999精品久久| 菠萝蜜一区二区| 色女人在线视频| 九色porny自拍| 看高清中日韩色视频| 久久精品视频在线观看| 日韩欧美成人精品| 国产美女精品人人做人人爽| 久久99精品久久久久久园产越南| av电影在线播放高清免费观看| 福利视频免费在线观看| 国产精品亚洲美女av网站| 日韩大片免费观看视频播放| 亚洲激情自拍视频| 久久精品国产精品青草| 一区二区三区韩国免费中文网站| 日韩av毛片| 秋霞福利视频| 欧美交换配乱吟粗大25p| 91精品国产综合久久香蕉| 亚洲激情国产精品| 亚洲成av人片一区二区梦乃| 成人性视频免费网站| 欧美1区2区视频| 国产在线一区不卡| 99在线播放| 在线看你懂得| 日韩欧美xxxx| 一区二区三区在线视频111| 国产精品偷伦一区二区| 日韩中文综合网| 日韩一区二区三区免费看| 亚洲欧美国产毛片在线| 国产高清精品在线| 欧美午夜电影在线观看| 成人资源在线| 中文字幕一区久| 看黄网站在线观看| 中文字幕在线观看第一页| 少妇人妻互换不带套| 中文字幕av日韩精品| 91亚洲精品久久久久久久久久久久| 欧美巨乳美女视频| 日韩av一区二区在线观看| 欧美色中文字幕| 亚洲不卡av一区二区三区| 中文字幕免费不卡| av电影一区二区| 国产成人精品免费一区二区| 日韩成人午夜电影| a91a精品视频在线观看| 欧美一区91|