雖然Rust和Go都是從上一代編程語言的錯誤中吸取教訓的現代編程語言,但它們以完全不同的方式管理并發,這對性能和開發人員體驗有巨大的影響。
但首先,我們為什么需要并發?
今天,大多數程序與需要一定時間才能返回響應的資源進行交互:例如網絡或磁盤。如果我們在等待網絡響應的同時完全阻塞程序的執行,這將是對硬件的一種相當低效的使用!
這就是為什么Go和Rust在等待I/O(輸入/輸出)時允許程序執行其他任務的語言特性。
任務是可以并發執行的抽象計算單元:多個函數可以(由程序)同時處理,但它們不一定(由CPU)同時執行(它的并行性需要多個線程)。
可以使用go關鍵字在Go中生成新任務:
go doSomething()go doAnotherThing()
在Rust中,需要使用spawn函數:
tokio::spawn(async move { do_something().await});tokio::spawn(async move { do_another_thing().await});
在這兩種情況下,任務都由語言的運行時同時處理。
運行時的目的是管理和調度不同的任務,以便有效地使用硬件。
圖片
Rust和Go的第一個不同之處。你不能改變Go運行時(除非你使用一個完全不同的編譯器,比如tinygo),它是內置在語言中的,而在Rust中,語言沒有提供運行時,你必須自己配置。
函數在等待某些東西(例如網絡)時將控制權交還給運行時。在Go中,這是由標準庫、語言和編譯器自動完成的,而在Rust中,它在到達await關鍵字時發生。
Stackfull協程又稱綠線程,或M:N線程(M個綠線程運行在N個內核線程上)是Go采用的并發模型。
在這個模型中,運行時管理輕量級(綠色)線程,并將它們調度到可用的硬件線程上。與內核線程一樣,每個任務都有自己的棧,如果需要,可以由運行時增加棧。
stackfull協程的第一個問題是,每個任務都有自己的棧,這意味著每個任務使用較少的內存量。從Go 1.22開始,線程程序使用的最小內存量是2 KiB,這意味著如果有10,000個并發任務在運行,程序將使用至少20 MiB的內存。
Stackfull協程的第二個問題是,運行時需要完全控制棧布局,這使得與其他語言(如C的FFI)的互操作性變得困難,因為運行時必須在能夠調用C代碼之前做一些準備棧的工作。這就是為什么CGO被認為是緩慢的(在現實中,CGO調用在30到75納秒內完成,在我看來這是相當快的)。
另一方面,Rust采用了無棧協程方法,其中任務沒有自己的棧。在Rust中,Future基本上是實現Future Trait的簡單結構,其中每個.await調用鏈被編譯成巨大的狀態機。
如果你正在用Python或c#開發,你可能已經知道async/await函數著色的巨大代價,其中同步函數不能調用async函數,反之亦然。
這就導致了許多問題,比如導致了生態系統的碎片化,其中的庫是不可互操作的,很難在程序中使用libA,因為你使用的是async而不是這個庫,而且還導致了開發人員的許多錯誤,他們阻塞了運行時的事件循環,降低了系統的性能。
這在Rust中也同樣存在,因為標準庫不提供與同步函數相同的異步函數(例如read讀取整個文件),并且因為不同的運行時甚至不能相互操作,如果你開始為tokio運行時編寫程序,你將很難將其移植到另一個運行時。
雖然這些都在Go中得到了解決,在Go中,一切都是同步的,編譯器和運行時在調用程序員看不見的異步函數時自動插入等待點,但這是以性能損失(內存和CPU)為代價的。
雖然Rust方法可以最大限度地利用機器,但它帶來了一個碎片化的生態系統,這給Rust的采用帶來了很大的麻煩。
本文鏈接:http://www.tebozhan.com/showinfo-26-97002-0.htmlRust的并發模型 vs Go的并發模型:Stackless協程 vs Stackfull協程
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 大數據時代,如何保證消息的順序性?