html5支援了web worker這樣的api,允許網頁在安全的情況下執行多線程代碼。不過web worker實際上受到很多限制,因為它無法真正意義上共享記憶體資料,隻能通過消息來做狀态通知,是以甚至不能稱之為真正意義上的“多線程”。
web worker的接口使用起來很不友善,它基本上自帶一個sandbox,在沙箱中跑一個獨立的js檔案,通過 postmessage和 onmessge來和主線程通信:
得到的結果可以發現,線程中得到的data的id增加了,但是傳回來之後,并沒有改變主線程的bundle中的id,是以,線程中傳遞的對象實際上copy了一份,這樣的話,線程并沒有共享資料,避免了讀寫沖突,是以是安全的。保證線程安全的代價就是限制了線上程中操作主線程對象的能力。
這樣一個有限的多線程機制使用起來是很不友善的,我們當然希望worker能夠支援讓代碼看起來具有同時操作多線程的能力,例如,支援看起來像下面這個樣子的代碼:
這段代碼裡面,我們啟動一個worker之後,能夠讓任意代碼跑在worker中,并且當需要操作ui線程(比如讀寫dom)時,可以通過this.runonuithread回到主線程執行。
那麼如何實作這個機制呢? 看下面的代碼:
上面這段代碼定義了一個threadworker對象,這個對象建立了一個運作thread.js的web worker,儲存了共享對象sharedobj,并且對thread.js發回的消息進行處理。
如果thread.js中傳回了一個ui_task消息,那麼運作這個消息傳過來的function,否則執行run的complete回調 我們看看thread.js是怎麼寫的:
可以看到,thread.js接收ui線程傳過來的消息,其中最重要的是thread_task,這是ui線程傳過來的需要worker線程執行的“任務”,由于function是不可序列化的,是以傳遞的是字元串,worker線程通過解析字元串成function來執行主線程送出的任務(注意在任務中将共享對象sharedobj傳入),執行完成後将傳回結果通過message傳給ui線程。我們仔細看一下除了傳回值returnvalue以外,共享對象sharedobj也會被傳回,傳回時,由于worker線程和ui線程并不共享對象,是以我們人為通過指派的方式同步兩邊的對象(這樣是否線程安全?為什麼?)
可以看到整個過程其實并不複雜,這麼實作之後,這個threadworker可以有以下兩種用法:
這樣的用法從形式和語義上來說都讓代碼具有良好的結構,靈活性和可維護性。
好了,關于web worker的用法探讨就介紹到這裡,有興趣的同學可以去看一下這個項目:https://github.com/akira-cn/workerthread.js (由于worker需要用伺服器測試,我特意在項目中放了一個山寨的httpd.js,是個非常簡陋的http服務的js,直接用node就可以跑起來)。