1. 程式人生 > 其它 >Web Worker,JS多執行緒,vue-worker

Web Worker,JS多執行緒,vue-worker

http://www.ruanyifeng.com/blog/2018/07/web-worker.html 阮一峰文件介紹web worker

摘錄:

一、概述
JavaScript 語言採用的是單執行緒模型,也就是說,所有任務只能在一個執行緒上完成,一次只能做一件事。前面的任務沒做完,後面的任務只能等著。隨著電腦計算能力的增強,尤其是多核 CPU 的出現,單執行緒帶來很大的不便,無法充分發揮計算機的計算能力。

Web Worker 的作用,就是為 JavaScript 創造多執行緒環境,允許主執行緒建立 Worker 執行緒,將一些任務分配給後者執行。在主執行緒執行的同時,Worker 執行緒在後臺執行,兩者互不干擾。等到 Worker 執行緒完成計算任務,再把結果返回給主執行緒。這樣的好處是,一些計算密集型或高延遲的任務,被 Worker 執行緒負擔了,主執行緒(通常負責 UI 互動)就會很流暢,不會被阻塞或拖慢。

Worker 執行緒一旦新建成功,就會始終執行,不會被主執行緒上的活動(比如使用者點選按鈕、提交表單)打斷。這樣有利於隨時響應主執行緒的通訊。但是,這也造成了 Worker 比較耗費資源,不應該過度使用,而且一旦使用完畢,就應該關閉

Web Worker 有以下幾個使用注意點。
(1)同源限制

分配給 Worker 執行緒執行的指令碼檔案,必須與主執行緒的指令碼檔案同源。

(2)DOM 限制

Worker 執行緒所在的全域性物件,與主執行緒不一樣,無法讀取主執行緒所在網頁的 DOM 物件,也無法使用document、window、parent這些物件。但是,Worker 執行緒可以navigator物件和location物件。

(3)通訊聯絡

Worker 執行緒和主執行緒不在同一個上下文環境,它們不能直接通訊,必須通過訊息完成。

(4)指令碼限制

Worker 執行緒不能執行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 物件發出 AJAX 請求。

(5)檔案限制

Worker 執行緒無法讀取本地檔案,即不能開啟本機的檔案系統(file://),它所載入的指令碼,必須來自網路。

應用場景
本人使用的vue開發,在使用echarts圖表時,後端返回資料量很大,又要對資料進行遍歷操作,還有圖表比較,需要將多張圖的資料拼合排序比對,資料很多,操作量很大,會造成頁面卡死,失去相應,遂使用web worker。

下面來看看vue中使用worker
git官方文件:https://github.com/israelss/vue-worker

https://blog.csdn.net/weixin_34080903/article/details/85999611?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase 原部落格。介紹了vue worker

摘錄:
安裝:

yarn add vue-worker

// or

npm install vue-worker --save

配置:

在main.js中:

import VueWorker from 'vue-worker';
Vue.use(VueWorker);
這會將屬性注入Vue(並將其傳遞給每個子元件),預設名稱為$worker,可以this.$worker在任何Vue元件內部使用該預設名稱進行訪問。

您可以在註冊外掛時更改該名稱:

import VueWorker from 'vue-worker'
Vue.use(VueWorker, '$desired-name')
使用


export default {
name: 'worker-test',
data() {
return {
worker: null,
}
},
mounted() {
// 通過this.$worker.run這個方法,跑起一個worker,
// worker是在另外的執行緒裡面跑的,所以可以在run的第一個引數函式裡面執行一個非常大計算的操作
// run方法像Promise一樣提供.then和.catch,then的引數就是run第一個引數函式的返回值
this.worker = this.$worker.run(n => n + 10, [2])
// 陣列中為傳遞給worker的引數,可以傳遞多個,此案例為2
.then(res => console.log(res)) // res為worker計算結束return返回的結果資料,可以在.then里根據返回結果繼續操作主執行緒後續任務
.catch(e => console.log(e)) // 報錯資訊
},
destroyed() {
// 通過賦值null的方式,釋放掉worker引用,這樣就可以關閉worker,這是作者在github上說的
this.worker = null
},
}
API介紹
下面來詳細介紹一下vue-worker的幾個api,也就是方法method。

.run(fun, [...args])

上面已經看到了這個方法,而且也有註釋說明。注意第二個引數是一個數組,陣列的個數和第一個引數fun的形參個數是一樣的。 你可能會有一個疑問:直接在fun函式體內使用當前變數不就好了麼?js本身的全域性變數特性在這裡不能用?這是因為worker是在另外一個執行緒中執行,跟當前頁面內的js指令碼不是在同一個執行緒,不共享記憶體空間,所以直接在fun函式體裡面使用另外一個執行緒的變數是找不到的,所以要通過函式引數的形式進行傳遞。而傳遞的實質,是使用了worker的postMessage方法,把第二個引數當做postMessage的內容,具體你可以閱讀這裡的原始碼。

run是一次性的,跑完這次,worker執行緒就會被關掉。想要持久化worker,可以使用下面的create來建立。

.create([...actions])

這個方法讓你建立一個worker物件(注意不是worker例項,你無法通過該物件直接操作worker,這個例項僅僅是一個js object,提供了幾個屬性介面)。

actions是一個數組,陣列的每個元素是一個含有兩個屬性的物件:

export default {
name: 'worker-test',
data() {
return {
worker: null,
}
},
created() {
this.worker = this.$worker.create([
{
message: 'pull-data',
func(data) {
data.forEach(...)
return data
},
},
{
message: 'run-task',
func(id) {
//...
},
}
])
},
mounted() {
let data = ...
this.worker.postMessage('pull-data', [data])
.then(res => console.log(res))
},
destroyed() {
this.worker = null
},
}
實際上.run方法是create方法和postMessage方法的合體,一次性把兩個方法的事都做了。

.postMessage(messageid, [...args])

這個在上面的程式碼裡面已經演示了。它不是this.$worker的方法,而是通過this.$worker.create之後得到的object的一個方法。使用這個方法跟worker原生的方法很像,當然,這裡的messageid就是上面actions數組裡面的某個物件的message欄位對應的那個。而args就是你要傳遞的資料。

你可能又會問了,這裡的[..args]是一個引數,還是說裡面的元素才是引數。其實很簡單,[...args]被用作了.apply的第二個引數:func.apply(null, [...args]),所以,...args對應的就是func的引數。

.postAll([...args])

這裡的postAll和上面的postMessage一樣,是create之後的那個object的一個方法,而不是this.$worker的,所以使用的時候,也只能用在create之後。

它的引數是一個數組,但是這個陣列的元素有三種形式,一種是不傳,一種是string:messageid,另一種是{message, [...func_args]}。其實都很好理解。

不傳

代表所有的actions都執行一次postMessage。

[string:messageid]

代表對應的messageid的那個action被執行postMessage。

[{message, [...func_args]}]

給指定的messageid傳引數。

export default {
name: 'worker-test',
data() {
return {
worker: null,
}
},
created() {
this.worker = this.$worker.create([
{
message: 'pull-data',
func(data) {
data.forEach(...)
return data
},
},
{
message: 'run-task',
func() {
//...
},
}
])
},
mounted() {

// 1. 不傳
this.worker.postAll().then([res1, res2] => {})

// 2. 字串形式
let data = ...
this.worker.postAll(['run-task']).then([res] => {}) // 僅'run-task'被postMessage

// 3. 物件形式(混合形式)
this.worker.postAll([
'run-task',
{
message: 'pull-data',
args: [data],
},
]).then([res1, res2] => {})
},
destroyed() {
this.worker = null
},
}
比較難把握的就是,這裡所有的傳入都要採用陣列的形式,理解上需要稍微思考下。

.register(action || [...actions])

同理,也是在<worker>的object物件上的方法。當你使用create之後,發現你的worker任務不夠用,要追加一個action或多個,那麼可以使用register來追加。action(s)和create是一模一樣的。

.unregister(message || [...messages]) 和register有點像,意思是當你某一個任務不想要了,可以通過unregister來取消這個任務。引數和register不一樣,直接使用messageid作為引數即可。

export default {
name: 'worker-test',
data() {
return {
worker: null,
}
},
created() {
this.worker = this.$worker.create([
{
message: 'pull-data',
func(data) {
data.forEach(...)
return data
},
},
{
message: 'run-task',
func() {
//...
},
}
])
},
mounted() {
// 1. 不傳
this.worker.postAll().then([res1, res2] => {})

// 2. 字串形式
let data = ...
this.worker.postAll(['run-task']).then([res] => {}) // 僅'run-task'被postMessage

// 3. 物件形式(混合形式)
this.worker.postAll([
'run-task',
{
message: 'pull-data',
args: [data],
},
]).then([res1, res2] => {
// 注意,這裡then裡面執行的是在主js執行緒裡面執行的,所以可以直接用this.worker
this.worker.unregister('run-task')
// 當你登出掉了,那麼下回你在post到run-task這個任務訊息時,就啥都不會發生了
})
},
destroyed() {
this.worker = null
},
}
關閉worker

在最前面的程式碼裡面已經提到了,外掛的作者指出,你是沒辦法拿到worker原始例項的,所以也就無法呼叫worker.terminate()或者在worker執行緒內部執行self.close()來關閉worker。create方法建立的不是worker例項,所以它內部有,但是沒有暴露出來。所以外掛沒有關閉worker的方法,你直接把worker物件釋放掉即可。我翻閱了原始碼,發現它只在呼叫run方法時才使用close,執行完run之後worker會被close,但是如果你使用create建立的worker,是不會被close的它會一直存在,直到你關閉瀏覽器。

原理

web worker是通過一個瀏覽器提供的Worker物件來建立的,建立的時候要傳入指定的javascript檔案作為worker執行緒的執行指令碼。worker執行緒內的指令碼有一些限制,比如只能拿到window.navigator的資訊,不能拿到完整的window物件。重點是,這裡我們沒有提供一個js檔案傳入worker執行緒,vue-worker是怎麼做到的呢?它利用了Blob來建立一個可執行的二進位制上下文,在通過這個上下文來呼叫我們傳入的function,就好像在記憶體中虛擬了一個內容是我們傳入的function的js檔案一樣。


————————————————
版權宣告:本文為CSDN博主「大德大威顧神」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/q759859479/article/details/106429900