在多線程程式設計中,多個線程同時存取共享資源(如變數、檔案、資料庫連線)時,若缺乏同步機制,極易發生競態條件,導致資料不一致或程式崩潰。C# 提供 lock 關鍵字作為互斥鎖的語法糖,是解決此類問題的基礎工具。
public class SharedResource {
private static readonly object _lockObj = new object();
private int _counter = 0;
public void Increment() {
lock (_lockObj) {
_counter++; // 非原子操作:讀取、加一、寫回
}
}
}
lock(obj) 編譯器會展開為 Monitor.Enter(obj) 與 Monitor.Exit(obj)(在 try/finally 區塊中確保釋放)。只有持有鎖的線程能進入臨界區,其他線程阻塞等待。
需求:多個郵件接收線程(每個郵箱一個實例)從 POP3 取信,存入 FTP,需按接收順序全域遞增編號。
class EmailProcessor {
public static int CurrentNumber = 0;
public void Process() {
// 錯誤:鎖住 this,每個實例的 this 不同,無法互斥
lock (this) {
int localNum = ++CurrentNumber;
}
}
}
原因:lock(this) 僅同步同一實例上的呼叫。多個 EmailProcessor 實例各自持有不同的 this,互不干擾,靜態變數 CurrentNumber 仍會發生競爭。
class EmailInfo {
public static readonly object SyncRoot = new object();
public static int CurrentNumber = 0;
}
class EmailProcessor {
public void Process() {
// 所有實例共用同一把鎖
lock (EmailInfo.SyncRoot) {
int localNum = ++EmailInfo.CurrentNumber;
}
}
}
核心原則:鎖物件必須是所有需同步的線程可見的同一個引用。靜態唯讀物件是最佳實踐。
lock(typeof(MyClass)) 或 lock("string") 可能被外部代碼鎖住,導致死鎖。Monitor 是可重入的,同一線程可多次 Enter,但需對應次數 Exit。Interlocked 原子操作、ReaderWriterLockSlim 讀寫分離、或無鎖資料結構。public class ThreadSafeCounter {
private readonly object _syncLock = new object(); // 實例級同步
// private static readonly object _syncLock = new object(); // 靜態級同步
private int _count;
public int Increment() {
lock (_syncLock) {
return ++_count;
}
}
}
掌握 lock 的正確用法,是編寫健壯多線程 C# 程式的基石。記住:共享資源、同一把鎖、最小臨界區。