Go語言的并發體驗幾乎能讓每個用過的人上癮:一個go關鍵字啟動協程,通過channel傳遞數據,運行時調度器在少量操作系統線程上就能支撐起成千上萬個goroutine。整個過程輕巧得像什么都沒發生。可一回到C語言的世界,這些便利全部歸零。于是一個試探性的問題浮了出來:只用POSIX線程,究竟能逼近到哪種地步?
最先涌上來的聲音多半是懷疑。原生操作系統線程的調度和內存成本,顯然無法與用戶態輕量級協程匹敵,并發數一拉高,上下文切換的損耗就會迅速顯現。有人會據此斷言:這條路走不通。但反對的一方同樣有底氣——絕大多數應用場景里,你真的需要幾萬個并發單元嗎?網絡服務、數據處理流水線、后臺任務,幾百甚至幾十個線程往往已經夠用。而且,直接使用pthreads意味著沒有額外的運行時依賴,每一行代碼的行為都透明、可打斷點,也更容易定位問題。
這場爭論的現場,就在作者為Solod(簡稱So)添加并發支持的時候。So是Go的一個嚴格子集,編譯后是一份純凈的C代碼,不帶運行時,也不帶垃圾回收器。作者需要為它注入并發能力,最終的選擇就是pthreads。他的判斷是:只要誠實地承認取舍,用POSIX線程能做的事情遠比想象中多。
整個并發工具棧的地基,只有兩個最基礎的POSIX原語:互斥鎖和條件變量。So標準庫里的sync.Mutex幾乎就是pthread_mutex_t的透明外衣。結構體只裝一個pthread_mutex_t字段,Lock方法內部調用pthread_mutex_lock,失敗就直接panic。把它翻譯成C后,結構體定義不變,只是把方法攤開成普通函數,生成的代碼像鏡像一樣干凈。條件變量sync.Cond同樣如此:包裹pthread_cond_t,配上Wait、Signal和Broadcast三個方法,翻譯后的C代碼也是一一對應。
有了這對搭檔,就可以接著構筑一次性初始化、線程池和channel等更復雜的構件。它們統統基于互斥鎖搭配一到多個條件變量組合而成。整棟建筑沒有隱藏的調度器魔法,每一個并發單元就是一個真實的操作系統線程。這對于追求可控制、可預測行為的程序來說,反而是一種優勢。性能的臨界點在哪里,完全取決于具體場景的負載特征,但它帶來的調試清晰度和底層可視性,在不少開發場景中足以抵掉部分損失。
最終的問題依然懸在那里:用pthreads寫出Go風味的并發,到底能走多遠?答案恐怕無法一刀切——它取決于你愿不愿意用系統線程的完全控制權,去換回那種輕若無物的便利。
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.