在多線程程式設計中,資料競爭是最常見也最棘手的問題。當多個線程同時存取共享資料時,若缺乏適當的同步機制,極易導致資料損毀或程式行為不可預期。臨界區是 Windows 提供的一種輕量級同步物件,專門用於保護單一進程內多線程對共享資源的獨佔存取。本文將詳細介紹 MFC 中 CCriticalSection 類的使用方法,並透過完整的程式碼範例示範如何建立線程安全的資料結構。
臨界區物件確保同一時間只有一個線程能進入受保護的程式碼區段。當線程嘗試進入已被佔用的臨界區時,會自動進入等待狀態,直到擁有權的線程釋放為止。與互斥量不同,臨界區無法跨進程使用,但效能更高、開銷更小,是單一進程內線程同步的首選方案。
MFC 透過 CCriticalSection 類封裝了 Windows 臨界區 API。使用前需在標頭檔包含 afxmt.h。基本操作只有兩個核心方法:
Lock():請求擁有權,若已被佔用則阻塞等待Unlock():釋放擁有權,喚醒等待中的線程以下範例展示如何將臨界區封裝於資料類別內部,對外提供線程安全的介面:
class CDataArray
{
private:
int iArray[10];
CCriticalSection CriticalSection; // 內建同步物件
public:
CDataArray() {}
~CDataArray() {}
void SetData(int iValue);
void GetDataArray(int aArray[10]);
};
// 寫入操作 - 完整鎖定保護
void CDataArray::SetData(int iValue)
{
CriticalSection.Lock();
for (int i = 0; i < 10; i++)
iArray[i] = iValue;
CriticalSection.Unlock();
}
// 讀取操作 - 同樣需要鎖定保護
void CDataArray::GetDataArray(int aArray[10])
{
CriticalSection.Lock();
for (int i = 0; i < 10; i++)
aArray[i] = iArray[i];
CriticalSection.Unlock();
}
建立全域資料實例,並啟動兩個工作者線程分別進行寫入與讀取:
CDataArray DataArray; // 共享資料實例
// 寫入線程:循環寫入 0-9,每次間隔 500ms
UINT Thread_WriteProc(LPVOID param)
{
for (int i = 0; i < 10; i++)
{
DataArray.SetData(i);
::Sleep(500);
}
return 0;
}
// 讀取線程:循環讀取 20 次並顯示訊息框
UINT Thread_ReadProc(LPVOID param)
{
int aArray[10];
for (int i = 0; i < 20; i++)
{
DataArray.GetDataArray(aArray);
char str[255] = {0};
for (int j = 0; j < 10; j++)
{
int len = strlen(str);
wsprintf(&str[len], "%d", aArray[j]);
}
::MessageBox((HWND)param, str, "Thread Read Proc", MB_OK);
}
return 0;
}
// 啟動線程
HWND hWnd = GetSafeHwnd();
AfxBeginThread(Thread_WriteProc, hWnd);
AfxBeginThread(Thread_ReadProc, hWnd);
Lock() 必須對應一個 Unlock(),建議使用 RAII 包裝類(如 CSingleLock)防止因異常導致死鎖Lock() 會導致死鎖,臨界區不支援遞迴鎖定臨界區是 MFC 多線程程式設計中最基礎也最重要的同步工具。透過將 CCriticalSection 封裝於資料類別內部,可有效隔離同步邏輯,降低使用者誤用風險。實務上建議搭配 CSingleLock 使用,確保異常安全與程式碼可維護性。