1. 程式人生 > 程式設計 >客戶端JavaScript的執行緒池設計詳解

客戶端JavaScript的執行緒池設計詳解

目錄
  • 1.介紹:
  • 2.準備工作:
  • 3.測試spark-md5是否正常工作:
  • 4.執行緒池設計
  • 5.spark-md5對檔案進行md5編碼
  • 6.大量檔案進行MD5加密並使用執行緒池優化
  • 總結

1.介紹:

本打算在客戶端進行機器學習演算法計算時應用執行緒池來優化,就像()演示的神經網路。但是由於各種原因不了了之了。本次遇到了一個新的問題,客戶端的MD5運算也是耗時操作,如果同時對多個字串或檔案進行MD5加密就可以使用執行緒池來優化。

2.準備工作:

到npm官網搜尋spark-md5,到其倉庫下載spark-md5.。該js檔案支援AMD,CommonJS和web工作執行緒的模組系統,我們在實現執行緒池時,執行緒工作程式碼交給web工作執行緒處理。

在這裡插入圖片描述

在這裡插入圖片描述

在這裡插入圖片描述

3.測試spark-md5是否正常工作:

建立一個,再建立一個worker.js用於儲存工作執行緒的程式碼。以下述程式碼測試,如果成功輸出MD5編碼,那麼準備工作完成。

客戶端網頁程式碼

<script>
    let worker = new Worker("worker.js")
    worker.postMessage("Danny")
    worker.onmessage = function({data}) {
        console.log(data)
        worker.terminate()
    }
</script>

工作執行緒程式碼

self.importScripts("spark-md5.js")

self.onmessage = function({data}) {
    self.postMessage(self.SparkMD5.hash(data))
}

4.執行緒池設計

1. 目標:本次執行緒池設計的目標是初始建立n個初始執行緒,能夠滿足任意個執行緒請求,超出n的請求並不丟棄,而是等待到出現空閒執行緒後再分配之。

2. 基本設計思路:為了基本滿足上述目標,至少要有一個執行緒分配功能,一個執行緒回收功能。

3. 執行緒分配功能設計:

  • 執行緒池滿指的是執行緒池已經沒有可用空閒執行緒
  • 通知物件是一個不可逆狀態機,可以用Promise物件來實現
  • 阻塞請求佇列儲存Promise物件的resolve方法即可
  • 儲存執行緒池中的執行緒使用陣列即可,陣列每個元素是一個物件,包括執行緒和執行緒狀態
  • 返回給使用者的可用執行緒還需要有執行緒在陣列中的下標,線上程釋放中會用到

在這裡插入圖片描述

4. 執行緒釋放功能設計:

  • 執行緒釋放功能需要接收一個引數,為執行緒的標識,3中設計該標識為陣列下標
  • 當執行緒釋放後,檢視阻塞請求佇列是否為空,如果不為空,說明有被阻塞的執行緒請求,此時令隊首元素出隊即可,執行resolve()通知物件的狀態變更為Fulfilled

在這裡插入圖片描述

5. 實現執行緒池:

class MD5Pool {
    // worker用於儲存執行緒
    worker = []
    // status是執行緒池狀態
    status = "Idle"
    // 阻塞請求佇列
    blockRequestQueue = []
    // size為使用者希望的執行緒池的容量
    constructor(size) {
        for(let i = 0; i < size; i ++)
            this.worker.push({
                worker: new Worker("worker.js"),status: "Idle"
            })
    }
    
    // 執行緒池狀態更新函式
    statusUpdate() {
        let sum = 0
        this.worker.forEach(({ status }) => {
            if(status === "Busy")
                sum ++
        })
        if(sum === this.worker.length)
            this.status = "Busy"
         else
            this.status = "Idle"
    }
    
    // 執行緒請求方法
    assign() {
        if(this.status !== "Busy") {
            // 此時執行緒池不滿,遍歷執行緒,尋找一個空閒執行緒
            for (let i = 0; i < this.worker.length; i++)
                if (this.worker[i].status === "Idle") {
                    // 該執行緒空閒,更新狀態為忙碌
                    this.worker[i].status = "Busy"
                    // 更新執行緒池狀態,如果這是最後一個空閒執行緒,那麼執行緒池狀態變為滿
                    this.statusUpdate()
                    // 返回給使用者該執行緒,和該執行緒的標識,標識用陣列下標表示
                    return {
                        worker: this.worker[i].worker,index: i
                    }
                }
        }
        else {
            // 此時執行緒池滿
            let resolve = null
            // 建立一個通知物件
            let promise = new Promise(res => {
                // 取得通知物件的狀態改變方法
                resolve = res
            })
            // 通知物件的狀態改變方法加入阻塞請求佇列
            this.blockRequestQueue.push(resolve)
            // 返回給請求者執行緒池已滿資訊和通知物件
            return {
                info: "full",wait: promise
            }
        }
    }
    
    // 執行緒釋放方法,接收一個引數為執行緒標識
    release(index) {
        this.worker[index].status = "Idle"
        // 阻塞請求佇列中的第一個請求出隊,佇列中儲存的是promise的resolve方法,此時執行,通知請求者已經有可用的執行緒了
        if(this.blockRequestQueue.length)
            // 阻塞請求佇列隊首出列,並執行通知物件的狀態改變方法
            this.blockRequestQueue.shift()()
        // 更新執行緒池狀態,此時一定空閒
        this.status = "Idle"
    }
}

5.spark-md5對檔案進行md5編碼

說明:

在3的測試中spark-md5只是對簡單字串進行MD5編碼,並非需要大量運算的耗時操作。spark-md5可以對檔案進行MD5編碼,耗時較多,實現如下。

注意:

spark-md5對檔案編碼時必須要對檔案進行切片後再加密整合,否則不同檔案可能會有相同編碼。詳情見github或npm。

// 在工作執行緒中引入spark-md5
self.importScripts("spark-md5.js")

let fd = new FileReader()
let spark = new self.SparkMD5.ArrayBuffer()

// 接收主執行緒發來的訊息,是一個檔案
self.onmessage = function(event) {
    // 獲取檔案
    let chunk = event.data
    // spark-md5要求計算檔案的MD5必須切片計算
    let chunks = fileSlice(chunk)
    // 計算MD5編碼
    load(chunks)
}

// 切片函式
function fileSlice(file) {
    let pos = 0
    let chunks = []
    // 將檔案平均切成10分計算MD5
    const SLICE_SIZE = Math.ceil(file.size / 10)
    while(pos < file.size) {
        // slice可以自動處理第二個引數越界
        chunks.push(file.slice(pos,pos + SLICE_SIZE))
        pos += SLICE_SIZE
    }
    return chunks
}

// MD5計算函式
asynYICMbieTqc function load(chunks) {
    for(let i = 0; i < chunks.length; i ++) {
        fd.readAsArrayBuffer(chunks[i])
        // 在這裡希望節約空間,因此複用了FileReader,而不是每次迴圈新建立一個FileReader。需要等到FileReader完成read後才可以進行下一輪複用,因此用await阻塞。
        await new Promise(res => {
            fd.onload = function(event) {
                spark.append(event.target.result)
                if(i === chunks.length - 1) {
                    self.postMessage(spark.end())
                }
                res()
            }
        })
    }
}

6.大量檔案進行MD5加密並使用執行緒池優化

下面的測試程式碼就是對上文所述的拼接

網頁程式碼

<input id="input" type="file" multiple onchange="handleChanged()"/>
<body>
    <script>
        class MD5Pool {
            worker = []
            status = "Idle"
            blockRequestQueue = []
            constructor(size) {
                for(let i = 0; i < size; i ++)
                    this.worker.push({
                        worker: new Worker("worker.js"),status: "Idle"
                    })
            }

            statusUpdate() {
                let sum = 0
                this.worker.forEach(({ status }) => {
                    if(status === "Busy")
                        sum ++
                })
                if(sum === this.worker.length)
                    this.status = "Busy"
                 else
                    this.status = "Idle"
            }

            assign() {
                if(this.status !== "Busy") {
                    for (let i = 0; i < this.worker.length; i++)
                        if (this.worker[i].status === "Idle") {
                            this.worker[i].status = "Busy"
                            this.statusUpdate()
                            return {
                                worker: this.worker[i].worker,index: i
                            }
                        }
                }
                else {
                    let resolve = null
                    let promise = new Promise(res => {
                        resolve = res
                    })
                    this.blockRequestQueuhttp://www.cppcns.come.push(resolve)
                    return {YICMbieTq
                        info: "full",wait: promise
                    }
                }
            }

            release(index) {
                this.worker[index].status = "Idle"
                // 阻塞請求佇列中的第一個請求出隊,佇列中儲存的是promise的resolve方法,此時執行,通知請求者已經有可用的執行緒了
                if(this.blockRequestQueue.length)
                    this.blockRequestQueue.shift()()
                this.status = "Idle"
            }
        }

        // input點選事件處理函式
        function handleChanged() {
            let files = event.target.files
            // 建立一個大小為2的MD5計算執行緒池
            let pool = new MD5Pool(2)
            // 計算切片檔案的MD5編碼
            Array.prototype.forEach.call(files,file => {
                getMD5(file,pool)
            })
        }

        // 獲取檔案的MD5編碼的函式,第一個引數是檔案,第二個引數是MD5執行緒池
        async function getMD5(chunk,pool) {
            let thread = pool.assign()
            // 如果info為full,那麼說明執行緒池執行緒已被全部佔用,需要等待
            if(thread.info === "full") {
                // 獲取執行緒通知物件
                let wait = thread.wait
                // 等到wait兌現時說明已經有可用的執行緒了
                await wait
                thread = pool.assign()
                let { worker,index } = thread
                worker.postMessage(chunk)
                worker.onmessage = function (event) {
                    console.log(event.data)
                    pool.release(index)
                }
            } else {
                let { worker,index } = thread
                worker.postMessage(chunk)
                worker.onmessage = function (event) {
                    console.log(event.data)
                    pool.release(index)
                }
            }
        }
    </script>
</body>

工作執行緒程式碼

self.importScripts("spark-md5.js")

let fd = new FileReader()
let spark = new self.SparkMD5.ArrayBuffer()

self.onm客棧essage = function(event) {
    // 獲取檔案
    let chunk = event.data
    // spark-md5要求計算檔案的MD5必須切片計算
    let chunks = fileSlice(chunk)
    // 計算MD5編碼
    load(chunks)
}

// 切片函式
function fileSlice(file) {
    let pos = 0
    let chunks = []
    // 將檔案平均切成10分計算MD5
    const SLICE_SIZE = Math.ceil(file.size / 10)
    while(pos < file.size) {
        // slice可以自動處理第二個引數越界
        chunks.push(file.slice(pos,pos + SLICE_SIZE))
        pos += SLICE_SIZE
    }
    return chunks
}

// MD5計算函式
async function load(chunks) {
    for(let i = 0; i < chunks.length; i ++) {
        fd.readAsArrayBuffer(chunks[i])
        // 在這裡希望節約空間,因此複用了FileReader,而不是每次迴圈新建立一個FileReader。需要等到FileReader完成read後才可以進行下一輪複用,因此用await阻塞。
        await new Promise(res => {
            fd.onload = function(event) {
                spark.append(event.target.result)
                if(i === chunks.length - 1) {
                    self.postMessage(spark.end())
                }
                res()
            }
        })
    }
}

隨機選取18個檔案進行MD5編碼,結果如下

在這裡插入圖片描述

總結

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注我們的更多內容!