Windows 是搶佔式多線程作業系統,理解線程狀態轉換、調度原理、同步機制與高精度計時,是編寫高性能、可靠 Windows 應用的基礎。本文系統梳理核心概念與實戰 API。
| 狀態 | 英文 | 描述 | 觸發條件 |
|---|---|---|---|
| 新建 | New | 線程對象創建,資源分配完成 | CreateThread / std::thread |
| 就緒 | Ready | 等待 CPU 時間片,在就緒隊列中 | 創建完成、時間片用完、同步對象釋放 |
| 運行 | Running | 正在 CPU 上執行指令 | 調度器分派、搶佔高優先級線程 |
| 阻塞 | Blocked/Wait | 等待資源/事件/同步對象,釋放 CPU | WaitForSingleObject、I/O、Sleep(>0) |
| 掛起 | Suspended | 被外部強制暫停,不佔用 CPU | SuspendThread、調試器斷點 |
CreateThread
(新建) ─────────→ (就緒) ◄──────────────┐
│ ↑ │ │
│ │ │ Time slice │
│ │ │ expired / │
│ │ │ Preemption │
│ │ │ │
▼ │ ▼ │
(運行) ────────→ (就緒) │
│ ▲ ▲ │ │
│ │ Wait │ │ Signal/ │
│ │ /I/O │ │ Release │
▼ │ │ │ │
(阻塞) ─────────┘ │ │
▲ │ │
│ Suspend │ Resume │
│ │ │
└──── (掛起) ◄──┘ │
▲ │ │
│ Debugger │ │
└───────────┘ (調試斷點) │
| 維度 | 阻塞 | 掛起 |
|---|---|---|
| 主動性 | 被動 (等待資源) | 主動 (外部調用 SuspendThread) |
| CPU 釋放 | 是,進入等待隊列 | 否,直接凍結,不經過調度器 |
| 恢復控制 | 由系統自動喚醒 (資源就緒) | 必須外部顯式調用 ResumeThread |
| 隊列管理 | 按等待對象分隊列 (信號量、互斥體等) | 單一掛起隊列,系統調度器忽略 |
| 典型用途 | 同步、I/O 等待 | 調試斷點、熱補丁、GC 安全點 |
關鍵洞察:掛起繞過調度器,系統不再管理該線程,直到 ResumeThread。濫用掛起易導致死鎖 (如持有鎖時被掛起)。
Sleep(0); // 放棄當前時間片剩餘部分,立即重新調度
Sleep(10); // 阻塞 10ms (實際精度 ~15.6ms,受系統時鐘中斷影響)
SwitchToThread();// 僅當有同優先級就緒線程時切換,否則繼續運行
DWORD SuspendThread(HANDLE hThread); // 返回前次掛起計數,歸零時恢復
DWORD ResumeThread(HANDLE hThread); // 必須成對調用,計數歸零才真正恢復
⚠️ 嚴重警告:任意線程掛起極易導致死鎖 (持有鎖/堆鎖/加載器鎖時被掛起)。僅限調試器、GC、熱補丁等極少數場景。
SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST); // -2 到 +2 相對進程優先級
SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL); // 實時優先級 (需提升權限)
GetThreadPriority(hThread);
ULONGLONG start = GetTickCount64();
// do something...
ULONGLONG end = GetTickCount64();
// 問題:若中途線程被掛起/阻塞,包含等待時間,非純 CPU 時間
BOOL GetThreadTime(
HANDLE hThread,
LPFILETIME lpCreationTime, // 創建時間 (1601.1.1 起 100ns 間隔)
LPFILETIME lpExitTime, // 退出時間
LPFILETIME lpKernelTime, // 內核態 CPU 時間
LPFILETIME lpUserTime // 用戶態 CPU 時間
);
// 使用示例:測量函數 CPU 耗時
FILETIME create, exit, kernel1, user1, kernel2, user2;
GetThreadTime(GetCurrentThread(), &create, &exit, &kernel1, &user1);
// 待測代碼...
GetThreadTime(GetCurrentThread(), &create, &exit, &kernel2, &user2);
ULONGLONG cpuTime1 = (*(ULONGLONG*)&kernel1 + *(ULONGLONG*)&user1);
ULONGLONG cpuTime2 = (*(ULONGLONG*)&kernel2 + *(ULONGLONG*)&user2);
double cpuMs = (cpuTime2 - cpuTime1) / 10000.0; // 100ns → ms
LARGE_INTEGER freq, start, end;
QueryPerformanceFrequency(&freq); // CPU 頻率 (每秒計數)
QueryPerformanceCounter(&start);
// 短耗時代碼塊 (假設不被搶佔)
QueryPerformanceCounter(&end);
double elapsedMs = (end.QuadPart - start.QuadPart) * 1000.0 / freq.QuadPart;
⚠️ 適用場景:極短代碼塊 (微秒級),假設測量期間不發生線程切換。長代碼塊請用 GetThreadTime。
class ScopedCpuTimer {
HANDLE hThread_;
ULONGLONG startKernel_, startUser_;
public:
ScopedCpuTimer() : hThread_(GetCurrentThread()) {
FILETIME c, e, k, u;
GetThreadTime(hThread_, &c, &e, &k, &u);
startKernel_ = *(ULONGLONG*)&k;
startUser_ = *(ULONGLONG*)&u;
}
~ScopedCpuTimer() {
FILETIME c, e, k, u;
GetThreadTime(hThread_, &c, &e, &k, &u);
ULONGLONG elapsed = (*(ULONGLONG*)&k - startKernel_) + (*(ULONGLONG*)&u - startUser_);
std::cout << "CPU Time: " << elapsed / 10000.0 << " ms\n";
}
};
void HeavyFunction() {
ScopedCpuTimer timer; // RAII 自動測量
// 計算密集型代碼...
}
掌握 Windows 線程調度本質,能讓你在性能分析、死鎖排查、實時系統設計中遊刃有餘。