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上運行其他碾壓其他所有對手