今天說說MFC的線程,當年用它實現中間件消息得心應手之時,可以實現一邊實時接收數據,一邊更新界面圖表圖文信息,順滑得讓人想吹聲口哨。?MFC 多線程它像給程序裝上了分身術,讓原本只能 “單任務跑腿” 的代碼,突然有了 雙重任務?的本事。
一、線程的底層邏輯
設計模式里有個工廠模式,在我的眼里,進程就像一整個工廠:有獨立的廠房(內存空間)、固定的設備(系統資源),是操作系統能調度的最小單位。而線程就是工廠里的工人—— 他們共享廠房里的工具(進程資源),但各干各的活,既能協作裝配一臺機器,也能分頭處理不同訂單。
比如你打開的 VC++6.0 是一個進程,里面敲代碼的編輯窗口、實時編譯的后臺進程、甚至右下角的拼寫檢查提示,都是不同的線程在工作。線程有自己的優先級:就像工廠里緊急訂單優先處理,高優先級線程(比如實時數據刷新)會搶占 CPU 資源,但如果一直讓 “急單工人” 霸占設備,其他線程就會餓死 —— 這就是當年調試時總遇到的 “界面假死” 根源。
二、兩類線程
MFC 里的線程分兩類,就像工廠里的兩種工人:
Worker Threads(工作線程) 是埋頭干活的 “流水線工人”。他們不碰界面,專門處理后臺任務 —— 比如我當年寫的串口數據解析、文件批量轉換。這類線程沒有消息循環,干完活就下班,簡單直接。
UI Threads(界面線程) 則是 “前臺接待員”。他們有自己的消息循環(就像接待臺的呼叫系統),能創建窗口、處理按鈕點擊等交互。當年做數據監控軟件時,我用 UI 線程單獨管理報警彈窗,就算主界面卡了,報警窗口仍能彈出來 —— 這是當時在C/S開發場景里可是能救命的設計。
三、CWinThread:MFC 給線程搭的 “腳手架”
MFC 用 CWinThread 類封裝了線程操作,就像給工人準備了標準化工具包。創建線程不用直接調用 Windows API,通過它能少寫很多重復代碼。
比如創建一個工作線程,核心就兩步:
// 1. 定義線程函數(工人要干的活)UINT DataProcessThread(LPVOID pParam){// pParam是傳遞的參數(比如數據指針)DataHandler* handler = (DataHandler*)pParam;while(handler->IsRunning()){handler->ProcessOnePacket(); // 處理一個數據包Sleep(10); // 讓出CPU給其他線程}return 0; // 線程結束}// 2. 啟動線程(招募工人)CWinThread* pThread = AfxBeginThread(DataProcessThread, // 線程函數&m_dataHandler, // 傳遞參數THREAD_PRIORITY_NORMAL, // 優先級0, // 棧大小(默認即可)CREATE_SUSPENDED, // 先掛起,準備好再運行NULL);if(pThread != NULL){pThread->m_bAutoDelete = TRUE; // 線程結束后自動釋放pThread->ResumeThread(); // 開始工作}
這段代碼里的 AfxBeginThread 是 MFC 的 “線程啟動器”。當年總忘了設 m_bAutoDelete,結果線程結束后內存沒釋放,調試時看到內存占用越來越高,才明白 MFC 的 “自動清理” 有多重要。
UI 線程的創建稍復雜些,需要派生 CWinThread 子類,重寫 InitInstance 函數初始化窗口。就像給接待員配專屬工作臺,得先把桌子椅子(窗口資源)準備好。
四、線程之間的協作
線程管理的精髓,在于 “該停的時候停好,該協作的時候不搶”。
早期不理解,寫程序時,我粗暴地用 TerminateThread 強制結束線程,結果經常丟數據 —— 這就像突然關掉工廠電源,工人手里的零件肯定會散落一地。正確的做法是 “溫柔通知”:用一個全局標志位告訴線程 “可以下班了”。
// 安全結束線程的例子class DataHandler{private:bool m_bRunning; // 線程運行標志public:DataHandler() : m_bRunning(false) {}void Start() { m_bRunning = true; }void Stop() { m_bRunning = false; } // 通知線程停止bool IsRunning() { return m_bRunning; }};
線程同步則是另一個大學問。多個線程搶著讀寫同一塊數據時,就像兩個工人搶一把扳手 —— 輕則數據錯亂,重則程序崩潰。MFC 提供了 CCriticalSection(臨界區)、CMutex(互斥量)等 “工具鎖”,我最常用臨界區,就像給扳手加個鎖,誰用誰開鎖,用完再還給別人。
// 用臨界區保護共享數據CCriticalSection m_csData; // 定義臨界區(鎖)vector<DataPacket> m_packets; // 共享數據// 線程1:添加數據void AddPacket(DataPacket pkt){CSingleLock lock(&m_csData, TRUE); // 上鎖m_packets.push_back(pkt);} // 離開作用域自動解鎖// 線程2:讀取數據vector<DataPacket> GetAllPackets(){CSingleLock lock(&m_csData, TRUE); // 上鎖vector<DataPacket> temp = m_packets;m_packets.clear();return temp;}
卡過早期做股票行情軟件的C++程序,看過K線圖的處理情況,行情接收線程和 UI 刷新線程總搶著訪問行情數據,用了臨界區后,K 線圖再也沒出現過 “跳空” 的怪象。
最后小結
現在用?Python、Go 時,線程(協程)的用法變了很多,但每次處理并發,總會想起 MFC 多線程的日子。那些調試線程死鎖的深夜,盯著調試器里的線程狀態發呆;涉世之初時,爺爺不乏為少加一個鎖導致程序在客戶現場崩潰的愧疚;還有第一次用多線程讓界面流暢運行時的興奮 —— 這些經歷比代碼本身更珍貴。
MFC 多線程就像編程界的 “老自行車”,現在看來有些笨拙,但它教會我的道理從未過時:好的并發設計,不是讓線程各自為戰,而是讓它們像默契的團隊一樣協作。就像工廠里的工人,各司其職又互相配合,才能高效運轉。未完待續...........