在工業控制與嵌入式系統開發中,多線程技術是實現高效並發處理的關鍵。本文將以最典型的串口通訊為切入點,詳細講解 Win32 平台下多線程串口程式的設計流程,並簡要介紹多線程在網絡通訊中的兩種主流架構模式。
在 Windows 平台上,串口設備被視為一種特殊的文件,操作方式與普通文件讀寫高度一致,主要分為三個步驟:
CreateFile 獲取設備句柄。GetCommState/SetCommState 配置 DCB 結構,設定波特率、資料位、停止位、奇偶校驗等。SetCommMask、WaitCommEvent 實現事件驅動的非同步收發。CreateFile 的關鍵參數說明:
lpFileName:如 "COM1"dwDesiredAccess:GENERIC_READ | GENERIC_WRITEdwShareMode:串口獨佔,必須為 0dwCreationDisposition:OPEN_EXISTINGdwFlagsAndAttributes:非同步 I/O 需指定 FILE_FLAG_OVERLAPPEDDCB 結構包含完整的串口參數,如 BaudRate、ByteSize、Parity、StopBits 等,配置前需先取得當前狀態再修改寫回。
Win32 提供豐富的通訊事件:
| 事件常數 | 含義 |
|---|---|
| EV_RXCHAR | 接收緩衝區有字元 |
| EV_TXEMPTY | 發送緩衝區已空 |
| EV_CTS / EV_DSR / EV_RLSD | Modem 控制訊號變化 |
| EV_ERR | 線路狀態錯誤 |
| EV_BREAK / EV_RING / EV_RXFLAG | 斷線、響鈴、特殊字元 |
使用 SetCommMask 啟用關注的事件,再呼叫 WaitCommEvent 等待事件發生,配合 ReadFile/WriteFile 完成資料傳輸。
範例專案 MultiThreadCom 建立兩個工作者線程,分別處理 COM1 與 COM2,採用最簡化的三線制(僅 RX/TX)連接,故僅關注 EV_RXCHAR 與 EV_TXEMPTY 兩種事件。
在 CMultiThreadComApp::InitInstance() 中依序開啟並配置兩個串口,設定波特率 9600,清空發送緩衝區:
hComm1 = CreateFile("COM1", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
DCB wdcb; GetCommState(hComm1, &wdcb);
wdcb.BaudRate = 9600; SetCommState(hComm1, &wdcb);
PurgeComm(hComm1, PURGE_TXCLEAR);
對話框 OnInitDialog 中以 CreateThread 建立兩條線程,傳入主視窗句柄以便線程通過 PostMessage 通知 UI:
hCommThread1 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Com1ThreadProcess, AfxGetMainWnd()->m_hWnd, 0, &nThreadId1);
每個線程進入無限迴圈,呼叫 WaitCommEvent 等待事件,根據事件類型執行收發:
DWORD WINAPI Com1ThreadProcess(HWND hWnd) {
DWORD wEven; SetCommMask(hComm1, EV_RXCHAR | EV_TXEMPTY);
while (TRUE) {
WaitCommEvent(hComm1, &wEven, NULL);
switch (wEven) {
case EV_TXEMPTY:
if (wTxPos < wTxLen) WriteFile(hComm1, com1Data.TxBuf[wTxPos++], 1, &wCount, NULL);
break;
case EV_RXCHAR:
if (com1Data.wRxPos < com1Data.wRxLen) {
ReadFile(hComm1, com1Data.RxBuf[com1Data.wRxPos++], 1, &wCount, NULL);
if (com1Data.wRxPos == com1Data.wRxLen) ::PostMessage(hWnd, COM_SENDCHAR, 0, 1);
}
break;
}
}
return TRUE;
}
資料結構 tagSerialPort 獨立管理每個串口的收發緩衝區與位置指標,實現線程間資料隔離。
Remon Spekreijse 開源的 CSerialPort 類將上述底層 API 封裝為易用的 C++ 類,展示了專業級的多線程同步設計。
CWinThread 衍生的工作者線程,通過 StartMonitoring/StopMonitoring/RestartMonitoring 控制生命週期。CRITICAL_SECTION 保護通訊資源,三個事件組成陣列 m_hEventArray:關閉事件(最高優先級)、Overlapped I/O 事件、寫入觸發事件。FILE_FLAG_OVERLAPPED,所有讀寫操作配合 OVERLAPPED 結構實現非阻塞。CommThread雙重等待策略:
WaitCommEvent 監聽硬體事件(接收字元、CTS 變化等)。WaitForMultipleObjects 監聽使用者控制事件(關閉、寫入請求)。收到 EV_RXCHAR 時呼叫 ReceiveChar 循環讀取所有可用字元,並以 SendMessage 逐字元通知擁有者視窗;收到寫入事件則呼叫 WriteChar 發送緩衝區資料。
ProcessErrorMessage 利用 FormatMessage 取得系統錯誤描述,彈出對話框提示。m_bThreadAlive 變為 FALSE,確保執行緒正常退出後再釋放資源。網絡伺服器開發中,多線程應用主要有兩種經典模式:
listen/accept,每新增一個連線即 CreateThread 建立子線程專責處理。從串口通訊的底層 API 到封裝類的設計藝術,再到網絡伺服器的架構抉擇,多線程程式設計的核心始終圍繞著「資源隔離、同步協作、生命週期管理」三大主題。掌握 Win32 串口多線程實戰,不僅能應對工控嵌入式開發,更能為理解高併發網絡編程奠定堅實基礎。