前三篇涵蓋了服務啟動、調度、控制處理的標準流程。但實際工程中,複雜業務 (高併發網絡、長事務、多線程池) 會暴露核心難題:
SetServiceStatus 才不會被 SCM 誤殺或卡死?TerminateThread 是大忌?優雅停止的正確姿勢是什麼?本文基於實戰踩坑經驗,給出工業級解決方案。
TerminateThread// CtrlHandler 中 ❌
case SERVICE_CONTROL_STOP:
TerminateThread(g_hWorkerThread, 0); // 災難!資源洩漏、鎖死、DLL 無通知、數據損壞
break;
後果:目標線程無執行 `finally`/`__finally`、析構函數、SEH 解鎖、關閉句柄、釋放鎖、刷新緩衝區的機會 → **死鎖、內存洩漏、數據庫損壞、句柄耗盡**。
**原則**:CtrlHandler 只負責 **「狀態回報」+「發送停止信號」**,工作線程 **主動輪詢/等待信號** 並 **自主清理退出**。
| 機制 | 適用場景 | 複雜度 | 性能/延遲 | 推薦指數 |
|---|---|---|---|---|
| 事件對象 (`CreateEvent`/`SetEvent`/`WaitForSingleObject`) | 單/少量工作線程、簡單停止/暫停信號 | 低 | 微秒級 | ⭐⭐⭐⭐⭐ (首選) |
| 原子標誌位 (`std::atomic | 極簡輪詢迴路、無阻塞等待需求 | 極低 | 納秒級 (輪詢間隔) | ⭐⭐⭐⭐ |
| IOCP 完成端口 (`PostQueuedCompletionStatus`) | 高併發服務器、已使用 IOCP 架構、數千連接 | 高 | 極低 (內核調度) | ⭐⭐⭐⭐⭐ (高性能首選) |
| 異步過程調用 (APC) (`QueueUserAPC`) | 需在線程特定上下文執行清理、或線程處於可預警等待 | 中 | 依賴線程進入可預警等待 | ⭐⭐⭐ |
| 管道/本地 Socket | 跨進程服務控制、或需網絡透傳控制指令 | 中高 | 毫秒級 | ⭐⭐⭐ |
| 視窗消息 (`PostThreadMessage`) | GUI 服務 (極罕見)、已有消息泵線程 | 低 | 消息泵延遲 | ⭐⭐ |
// 全局/類成員
HANDLE g_hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 手動重置,初始非觸發
HANDLE g_hPauseEvent = CreateEvent(NULL, TRUE, TRUE, NULL); // 初始觸發 (運行狀態)
std::atomic g_serviceState = SERVICE_RUNNING; // 當前邏輯狀態
// CtrlHandler
VOID WINAPI CtrlHandler(DWORD dwCtrl) {
switch (dwCtrl) {
case SERVICE_CONTROL_STOP:
// 1. 立即回報 PENDING
ReportStatus(SERVICE_STOP_PENDING, 1, 30000);
// 2. 發信號通知所有工作線程
InterlockedExchange(&g_serviceState, SERVICE_STOP_PENDING);
SetEvent(g_hStopEvent); // 廣播停止
// 3. 可選:喚醒可能阻塞在 IO 的線程 (CancelIoEx, closesocket, shutdown)
break;
case SERVICE_CONTROL_PAUSE:
ReportStatus(SERVICE_PAUSE_PENDING, 1, 10000);
InterlockedExchange(&g_serviceState, SERVICE_PAUSE_PENDING);
ResetEvent(g_hPauseEvent); // 讓線程在 WaitForSingleObject(g_hPauseEvent) 處阻塞
break;
case SERVICE_CONTROL_CONTINUE:
ReportStatus(SERVICE_CONTINUE_PENDING, 1, 5000);
InterlockedExchange(&g_serviceState, SERVICE_CONTINUE_PENDING);
SetEvent(g_hPauseEvent); // 釋放繼續
break;
}
}
// 工作線程主迴路
DWORD WINAPI WorkerThread(LPVOID) {
while (true) {
// 1. 檢查停止信號 (零開銷原子讀取)
if (g_serviceState == SERVICE_STOP_PENDING || WaitForSingleObject(g_hStopEvent, 0) == WAIT_OBJECT_0) {
break; // 跳出迴路進入清理階段
}
// 2. 暫停支持:等待繼續事件 (不消耗 CPU)
WaitForSingleObject(g_hPauseEvent, INFINITE);
if (g_serviceState == SERVICE_STOP_PENDING) break; // 暫停期間收到停止
// 3. 執行核心業務邏輯 (處理請求、計算、IO...)
DoWorkCycle();
}
// 4. 優雅清理階段 (無超時壓力,但盡快)
CleanupResources(); // 關閉 Socket、提交事務、釋放鎖、Flush 緩衝
// 5. 最後由主線程 (或最後一個工作線程) 回報 STOPPED
// 通常在 ServiceMain 等待所有工作線程結束後統一回報
return 0;
}
// ServiceMain 協調
VOID WINAPI ServiceMain(...) {
// ... 初始化 ...
ReportStatus(SERVICE_RUNNING, 0, 0);
// 創建工作線程池
std::vector threads;
for (int i=0; i
SetServiceStatus 回報策略——避免 SCM 超時誤殺| 錯誤代碼 | 後果 | 修正 |
|---|---|---|
| 啟動時直接回報 RUNNING,初始化耗時 40 秒 | SCM 30 秒超時殺進程 (錯誤 1053) | 啟動即回報 START_PENDING,分步驟遞增 CheckPoint,WaitHint 設 60000+ |
| 停止時回報 STOP_PENDING 後不再更新 CheckPoint,清理耗時 50 秒 | SCM 等待 WaitHint (如 30s) 後殺進程 | 清理過程中定期 (每 5-10s) 遞增 CheckPoint、更新 WaitHint |
| 收到 STOP 直接回報 STOPPED (CheckPoint=0),但線程還在寫數據 | 數據截斷、文件損壞、SCM 以為已停實際未停 | 回報 STOP_PENDING,等線程真退出再回報 STOPPED |
| SHUTDOWN 時回報狀態 | 系統關機不等待,白白增加代碼複雜度 | SHUTDOWN 不調用 SetServiceStatus,只做極簡清理 |
g_status.dwWin32ExitCode = NO_ERROR; // 正常退出
// 異常退出時:
g_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; // 0x424
g_status.dwServiceSpecificExitCode = MY_INTERNAL_ERROR_DB_CONN_FAILED; // 自定義碼
配合 **事件日誌** (`ReportEvent`) 記錄詳細錯誤上下文 (堆棧、配置、錯誤碼),實現「無 UI 可診斷」。
掌握這些,你的服務就能從「能安裝啟動」進化為「生產級可靠運行、故障可診斷、升級可平滑」的基礎設施組件。