執行緒與並行

共享的可變狀態(shared mutable state)

如果你只從本欄位記住一句話,就記這句。幾乎每個並行錯誤都追溯到「共享的可變狀態」——不只一條執行緒搆得到(共享)且能改變(可變)的資料。想像一塊共享白板,好幾個人被允許同時擦掉重寫,卻沒有輪流的規則。遲早會有兩人互相覆蓋,白板最後就錯了。那塊白板就是共享的可變狀態,而那團混亂正是並行的執行緒能對共享記憶體做出的事。

把這個詞拆成兩部分,因為各自都不可或缺,去掉任一個危險就消失。共享,指不只一條執行緒看得到同一筆資料——一個全域變數、一個靜態、或任何透過兩條執行緒都握著的指標所及的堆積物件。可變,指資料能改變——至少一條執行緒會寫它。把兩者合起來,你就有了一個地方:一條執行緒的寫入能與另一條執行緒的讀或寫相撞,而作業系統可以任意順序把它們交錯。如果資料共享但永不改變(不可變),就沒有衝突——大家都只是讀。如果資料可變但不共享(某條執行緒自己的堆疊區域變數),也沒有衝突——只有一條執行緒碰它。這危害需要兩者同時成立。

這個框架為何如此有用:它把「並行很可怕」變成一張具體的檢查清單。要找出你的並行錯誤,先找出共享的可變狀態,再問它是怎麼被保護的。所有標準解法都攻擊這座三腳架的其中一腳:讓資料不共享(給每條執行緒自己的副本,或用執行緒區域儲存)、讓它不可變(只在它建好後讀它)、或——當你真的需要共享可寫的資料時——用同步機制(互斥鎖、原子操作)守住每一次存取,讓執行緒輪流而非相撞。Field n 大致就是最後這個選項的型錄。

一個全域 int requests = 0;,許多處理請求的執行緒各自對它做 requests++,這就是共享的可變狀態。改成給每條執行緒自己的區域計數器、最後再加總,危害就消失了——資料在被改動時不再是共享的。

這錯誤需要兩條腿:共享「且」可變。去掉任一條,衝突就消失。

共享唯讀資料是安全的;危險在於共享正被寫入的資料。常見的意外:若底層物件會變,一個 const 指標或一個「getter」仍可能暴露可變的共享狀態——重點是資料本身的可變性,而非存取的形式。

又称
shared writable data共享可寫資料