針對「多線程環境下函數是否會衝突」的常見疑問,系統梳理線程衝突定義、必要條件、線程安全資源分類、線程安全函數判定準則,以及互斥鎖正確使用姿勢。
定義:兩個或多個線程同時對同一共享資源操作導致的問題。
經典計數器超額示例:
int count = 0;
void ThreadMethod() {
while (true) {
if (count >= 100) break; // 檢查
DoSomething();
count++; // 非原子操作:讀-改-寫
}
}
多線程下 count++ 非原子,導致「檢查通過但實際已超額」的競態條件。
結論:只能從共享資源入手解決衝突。
定義:不同線程同時使用不導致衝突的資源。
const decimal PI = 3.14m;string 實例(.NET 不可變)、Type、Assembly 等不可變類型實例。readonly 字段初始化後不可變,屬線程安全。
static string s = "abc"; 變量 s 可被修改指向新字符串,變量不安全,但字符串實例安全。
判定準則:函數內僅使用線程安全資源,或僅使用局部變量(棧上,線程隔離)。
static int Add(int a, int b) => a + b; // 參數按值傳遞,局部運算
static string GetConnStr() => connectionString; // 只讀常量
static void Swap(ref int a, ref int b) { ... } // ref 引用外部變量
static int Add(INumber a, INumber b) => a.Number + b.Number; // 訪問堆上對象屬性
重載運算符本質是方法調用,若操作共享可變狀態則不安全。
保護共享資源的標準手段:lock(obj) { ... }。
// 差:整個 DoSomething 在鎖內,退化為單線程
lock (typeof(Env)) { if (count++ >= 100) break; DoSomething(); }
// 優:僅保護共享變量讀寫
lock (typeof(Env)) { if (count++ >= 100) break; }
DoSomething(); // 耗時操作移出鎖
集合類提供 SyncRoot 作為專用鎖對象,防止繞過封裝直接操作內部數組(如 _list)。lock(coll.SyncRoot) 而非 lock(coll)。
Concurrent*)。lock 保護最小臨界區。private readonly object _lock = new() 或集合 SyncRoot。