1. 程式人生 > >關於協程:nodejs和golang協程的不同

關於協程:nodejs和golang協程的不同

eight fun 插入 ber ise 最大 機制 引用 data

nodejs和golang都是支持協程的,從表現上來看,nodejs對於協程的支持在於async/await,golang對協程的支持在於goroutine。關於協程的話題,簡單來說,可以看作是非搶占式的輕量級線程。

協程本身

一句話概括,上面提到了

"可以看作是非搶占式的輕量級線程"。

在多線程中,把一段代碼放在一個線程中執行,cpu會自動將代碼分成碎片,並在一定時間切換cpu控制權,線程通過鎖機制確保自己使用的資源在cpu執行別的線程的代碼時被修改(占用的內存堆棧、硬盤數據資源等),也就是說通過鎖機制,

線程a在一塊內存中創建了一個變量,線程a代碼還沒結束,cpu切換去執行線程b了,但是由於鎖,線程b無法使用這塊內存。

如果僅在單核單線程cpu下來看,多線程和多協程沒有任何區別,因為線程不能並行,只能是cpu分碎片執行。

協程就是類似這個意思。協程是線程內的東西(暫且不談多線程下的協程),當協程遇到阻塞時,就切換線程控制權,讓線程去執行另外一個協程,只不過這個過程是排隊的。這和nodejs的事件輪詢是一回事,在nodejs中先告知系統我現在要

讀取文件了,系統讀取,io阻塞了,nodejs去執行下一段代碼B,執行完後檢查阻塞是否等待完畢,如果等待完畢就把結果推到事件隊列背後去執行回調函數。這一段話我用協程的意思來表達一下,把讀取文件,讀取結束後執行相應操作放在一個協程a

內,執行代碼B放在一個協程b內,線程執行協程a,a遇到io阻塞了,切換線程控制權,執行協程b,b執行結束,切換線程控制權,執行協程a。對於js用戶來說,協程是回調的另一種表現形式。

function sleep(ms){

return new Promise((resolve,reject)=>setTimeout(

()=>resolve(),ms

))

}

(async function (){

await sleep(3000)

console.log("你好")

}())

(async function (){

await sleep(3000)

console.log("世界")

}())

可以這麽看,執行async函數就是運行一段協程代碼,await關鍵字就是切換協程,在await後就去執行其他協程的代碼了。

func deferPrint(str string){

time.Sleep(time.Second*2)

fmt.Println(str)

}

func main(){

go deferPrint("你好")

go deferPrint("世界")

//如果主協程不阻塞,永遠不會切換

time.Sleep(time.Seconds*2)

}

golang的go關鍵字就是將一段代碼放在一個協程裏,線程選擇協程運行,碰見阻塞就自動切換協程運行,但需要註意的是,golang不會因為一個協程運行結束就自動切換,必須是阻塞之後

核心區別

鎖機制

golang的協程是可以帶鎖的 Lock.Mutex() Unlock(),nodejs是號稱永遠不會死鎖也根本沒有鎖這回事。

沒有鎖會導致的問題在於占用的資源被輕易修改

比如讀取一個文件,如果該文件為0kb我就寫一個字符串進去,如果大於0kb,我就不執行任何操作。協程中會有兩次阻塞,第一次是讀取該文件,判斷文件大小,第二次是寫入。假如有兩個函數簽名如下

async function getFileSize(filename) : number

async function writeFile(data,filename) :bool

async function exec(){

size = await getFileSize("./test.txt");

if(size==0){

await writeFile("你好","./test.txt")

console.log("ok!")

}

}

如果我第一次判斷結束後另一個協程裏執行了插入操作,那麽幾個函數的執行順序就會變成

A: getFileSize()檢查文件大小,協程阻塞,切換協程

B: 寫入文件,協程阻塞,切換協程

A: getFileSize()檢查文本大小結束,可以插入,執行writeFile()

但是此時隊列中還有一個B協程的插入操作會在A之前執行,A協程對此不知道,以為文件還是0kb

如果文件被上鎖了

A: getFileSize()檢查文件大小,協程阻塞,切換協程

B: 寫入文件,哦——協程A鎖住了這個文件,那我等他釋放把,切換協程

A: getFileSize()完成,插入操作

B: 哦——協程A還在占用,那我接著等

A: 搞定了,釋放鎖,我已經沒有什麽要執行的了,把我從隊列裏刪掉吧

B: 協程A釋放了文件的鎖,現在我可以寫入了

線程支持

golang之所以要支持鎖協程,我想是為了多線程支持。golang中可以啟用多個線程並行執行相同數量的協程。

nodejs受限於v8的isolate機制,只能跑在單線程中。所有代碼無法並行執行,無法處理計算密集型應用場景。

切換機制

nodejs使用await阻塞協程,手動切換線程控制權,node的協程是c++控制的,c++裏寫了這個函數可以被推入事件隊列就能夠用promise封裝成協程

golang在協程阻塞時自動切換協程,所以在寫golang的時候所有的代碼可以都寫同步代碼,然後用go關鍵字去調用,golang的協程是自己規定的,所有

函數在阻塞時都必須切換線程控制權

取返回值

nodejs中async函數是能直接返回值的

golang只能傳遞一個引用的channel

總的來說golang和nodejs應用場景不同。nodejs適合前端鼓搗,用plug/ejs配合express/koa2從服務器http請求數據後再填到模版引擎裏

golang類似與小c++,最大的亮點就是使用協程管理多線程

對語言來說,不應該選邊站,但還是捧一波c#,除了只能在.net上運行其他碾壓其他所有對手

關於協程:nodejs和golang協程的不同