執行緒與並行

每條執行緒各自的堆疊 vs 共享記憶體(per-thread stack vs shared memory)

當好幾條執行緒在一個程式裡跑時,自然的問題是:它們共享什麼、又有什麼是私有的?乾淨的答案是:每條執行緒得到自己的堆疊,而堆積、全域變數與程式碼則由它們全體共享。所以函式裡的區域變數,對正在跑那個函式的那條執行緒而言是私有的;但透過堆積或全域所及的任何東西,都是每條執行緒都看得到、也都能改的公共地帶。

回想一下,堆疊是存放函式區域變數、其引數、以及回到呼叫者的返回位址的記憶體區域;它隨著呼叫的進行與返回而成長與縮小。因為每條執行緒走自己的呼叫鏈,每條執行緒都需要自己的堆疊——所以作業系統會給每條新執行緒一塊全新、屬於它自己的堆疊區域。因此區域變數住在各執行緒私有的堆疊上,天生就是私有的:執行緒 A 的區域 int counter 和執行緒 B 的區域 int counter 是位在兩個不同位址的兩個不同盒子。堆積則相反,是一個共享的池子:malloc() 來的一塊、或任何全域或靜態變數,都住在一個每條執行緒都看得到的位址。如果兩條執行緒握著指向同一個堆積物件的指標,它們就瞄準了完全相同的那批位元組。

這對安全為何重要:私有堆疊自動是安全的——沒有別的執行緒搆得到你的區域變數,所以區域變數從不需要上鎖。危險在共享區域。當兩條執行緒能搆到同一個全域或同一個堆積物件、而其中至少一條會寫入的那一刻,你就有了「共享的可變狀態」,你必須協調它們,否則就有損壞的風險。由此自然得出一條實用準則:能放堆疊就放堆疊、能保持區域就保持區域;把每個共享的堆積物件與全域都當成需要計畫的東西。另外,永遠不要把指向某條執行緒區域變數的指標,傳進一條活得比那個函式久的執行緒——當那個函式返回時,堆疊框被回收,那個指標就懸置了。

int total = 0;(一個全域)是每條執行緒共享的同一個盒子——多條執行緒寫入它很危險。在某個執行緒函式內,int i = 0; 每次都是該執行緒自己堆疊上的一個全新盒子——安全,因為沒有別的執行緒搆得到它。

堆疊區域變數是私有且安全的;共享的堆積與全域才是危害所在。

每條執行緒的堆疊較小且大小固定(常為幾 MiB),所以深層遞迴或巨大的區域陣列可能撐爆某條執行緒的堆疊,即使整體記憶體還很空。共享堆積才是放大筆或共享資料的地方。

又稱
thread stack vs shared heap私有堆疊與共享堆積