JavaScript中的進程、線程和協程

IvanGallard 7年前發布 | 18K 次閱讀 進程 線程 JavaScript開發 JavaScript

這周一直在編前端構建的腳本,用到了多進程去解決一個效率問題。期間差了很多進程、線程、協程的資料,在這里記錄回顧一下。

概念

關于進程、線程、協程的概念這里就不再贅述了,具體的可以參考wiki百科:

簡單概括一下它們間的區別

就是相對線程和協程,進程更獨立,有自己的內存空間,所以進程間通信比較困難。線程比進程輕量級,屬于同一進程的多個線程間可以共享全部資源。協程與線程類似,不同點在于,線程由系統控制切換,協程是由用戶控制切換。

那么,控制切換,指的是控制什么的切換呢?

在一個進程中執行的程序,有時需要同時處理多個工作,這時我們可以創建多個線程,讓每個線程處理一個工作。但是,進程只有一個。就好比一個人,你給他分配了多個工作,幫他把每個工作單獨拉了一個列表,可還是他一個人干,他只能一會兒干干這一會兒干干那,來模擬多個工作同時進行的狀態,這就是所謂的系統控制切換,系統不停的在多個線程間切換來達到并行的效果。你可能會說,那根一件一件干不是一樣嗎?沒錯,是一樣的,在只有一個cpu的電腦上,用不用多線程程序執行的時間是一樣的。但是,如果這個人長了兩個腦袋呢?那么他就能同時處理兩件工作了。多核cpu就是那個長了好多個腦地的人……而協程的切換是要由用戶手動來控制的,所以協程并適合并行計算,而更多的用來優化程序結構。

js都支持嗎?

這要看js在什么環境運行。

在瀏覽器中,可以通過webworkers創建進程,可以通過async/await,yield/Generator/GeneratorFunction實現協程,控制程序切換。

在node中,除了可以使用上面瀏覽器中可以使用的方法,還可以通過cluster,child_process創建進程,通過libuv,tagg創建線程

剛才提到的那些都是啥?怎么用?

webworkers

簡單點兒說就是使用webworkers你可以在全新的環境中運行一個你指定的js文件。這個全新的環境是獨立的,既一個全新的進程,有點兒像一個新iframe還沒有window.top,window.parent屬性,哈哈……

webworkers創建的進程和主進程之間可以通過message事件傳遞消息,但是消息只能是字符串,所以想要傳對象和數組就只能傳json了……這也是他不方便的地方。

具體使用方法可以看MDN上的文章: 使用 Web Workers

async/await

async/await是es7中新加的兩個關鍵字,async 可以聲明一個異步函數,此函數需要返回一個 Promise 對象。await 可以等待一個 Promise 對象 resolve,并拿到結果。

其實就是類似匯編的寄存器和跳轉指令……呃,通俗的說就是可以根據狀態跳轉態另一個函數半中間。

由于es7還未在各個環境實現,想要使用的話還的用一些babel-polyfill之類的庫做兼容……

更詳細介紹請看阿阮的文章: 異步操作和Async函數

yield/Generator/GeneratorFunction

generator是es6中新增的函數,本質是可以將一個函數執行暫停,并保存上下文,再次調用時恢復當時的狀態。但是用來解決協程切換的問題貌似有點兒濫用特性的感覺呢……

更詳細介紹請看阿阮的文章: Generator 函數

cluster

cluster是node官方提供的一個多進程模塊,效果和C語言的fork函數類似,當前文件完全重新執行一遍,通過cluster.isMaster判斷是不是主進程,在區分不同的操作。進程間通過事件回調來通信,NodeJS 0.6.x 以上的版本開始支持。

示例代碼就不放了,node官方文檔上寫的很詳細: cluster

child_process

node自帶的child_process模塊里的fork函數可以實現類似瀏覽器里webworkers的效果,使用方法和webworker一毛一樣,都是通過讀取新文件開啟新進程,通過message通信。

具體介紹請看文檔: child_process.fork(modulePath[, args][, options])

官方文檔沒有示例,下面給出一個web服務接收參數計算斐波那契數組的例子:

index.js

var express = require('express');
var fork = require('child_process').fork;
var app = express();
app.get('/', function(req, res){
    var worker = fork('./work_fibo.js') //創建一個工作進程
    worker.on('message', function(m){//接收工作進程計算結果
        if('object' === typeof m && m.type === 'fibo'){
            worker.kill();//發送殺死進程的信號
            res.send(m.result.toString());//將結果返回客戶端
        }
    });
    worker.send({type:'fibo',num:~~req.query.n || 1});//發送給工作進程計算fibo的數量
});
app.listen(8124);

work_fibo.js

var fibo = functionfibo(n){//定義算法
    return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
}
process.on('message', function(m){
//接收主進程發送過來的消息
    if(typeof m === 'object' && m.type === 'fibo'){
        var num = fibo(~~m.num);
        //計算jibo
        process.send({type: 'fibo',result:num})
        //計算完畢返回結果
    }
});
process.on('SIGHUP', function(){
    process.exit();//收到kill信息,進程退出
});

libuv

libuv是node底層實現使用的c++庫……呃,所以如果你想使用這個庫來實現多線程,那么你就得編寫c++的代碼了,不得不說,要想真正理解程序的本質,不多掌握幾門語言真是不行啊……

對c++不了解我就不瞎BB了,推薦兩篇文章延伸閱讀:

tagg

tagg(Threads a gogo for Node.js)是Jorge Chamorro Bieling開發的一個node包。使用c語言phread庫實現的多線程。

還是那剛才的斐波那契數組計算為例:

var Threads = require('threads_a_gogo');//加載tagg包
functionfibo(n){//定義斐波那契數組計算函數
    return n > 1 ? fibo(n - 1) + fibo(n - 2) : 1;
}
var t = Threads.create().eval(fibo);
t.eval('fibo(35)', function(err, result){//將fibo(35)丟入子線程運行
    if (err) throw err; //線程創建失敗
    console.log('fibo(35)=' + result);//打印fibo執行35次的結果
});
console.log('not block');//打印信息了,表示沒有阻塞

最后結果:

not block
fibo(35)=14930352

我們可以看到執行效果與webworker類似,不同的是通信使用了異步回調的方式。

值得一提的是tagg包目前只能在linux下安裝運行,這里再推薦一個tagg2包,是跨平臺的。

這里需要重點提一下的是,不論tagg還是tagg2包都是利用phtread庫和v8的v8::Isolate Class類來實現js多線程功能的。

Isolate代表著一個獨立的v8引擎實例,v8的Isolate擁有完全分開的狀態,在一個Isolate實例中的對象不能夠在另外一個Isolate實例中使用。嵌入式開發者可以在其他線程創建一些額外的Isolate實例并行運行。在任何時刻,一個Isolate實例只能夠被一個線程進行訪問,可以利用加鎖/解鎖進行同步操作。

換而言之,我們在進行v8的嵌入式開發時,無法在多線程中訪問js變量,這條規則將直接導致我們之前的tagg2里面線程執行的函數無法使用Node.js的核心api,比如fs,crypto等模塊。

延伸閱讀:

總結

經過以上的學習,我們大概應該了解到進程、線程、協程的使用場景了,進程、線程適合用來處理計算密集型操作,協程適合用來優化代碼結構,解決回調函數嵌套問題。線程比進程更輕,更節省資源,但是由于上面提到的線程問題,針對一些可以使用js原生的大量計算或循環還可以用用,涉及到使用nodejs核心api的操作,就要用進程解決了。

p.s. 我的問題

我在工作中使用的是fis配合grunt調用打包。由于要同時打包多個項目,grunt和fis都會定義全局變量,各個模塊之間的配置可能會相互影響,各個模塊在打包過程中又沒有相互的通信,同時為了提高效率,非常時候適合使用多進程的方式來運行腳本。所以用cluster實現了多進程打包的。

最后,祝大家新年快樂,1號胖三斤,3號金三胖~ ┑( ̄Д  ̄)┍

 

來自:http://brooch.me/2016/12/30/process-thread-and-coroutine-in-javascript/

 

 本文由用戶 IvanGallard 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!