多線(xiàn)程技術(shù)的引入,不僅可以挖掘潛在的CPU空閑時(shí)間,而且還可以提高應用程序的反應速度,其優(yōu)點(diǎn)在有多個(gè)任務(wù)需要完成、有巨大數據流量的程序中反映得尤為明顯。而隨著(zhù)VisualC++的引入,其靈活的線(xiàn)程實(shí)現機制使得程序員從繁瑣的Windows編程中解脫出來(lái)。關(guān)于多線(xiàn)程基本機理和實(shí)現方法近年來(lái)有許多文章介紹,這里不再贅述。本文將側重于比較在工控程序中采用各種線(xiàn)程類(lèi)型和同步方法的優(yōu)劣,并給出一個(gè)實(shí)用的、有較廣適應性的程序主體框架。1 各種線(xiàn)程類(lèi)型和同步方法 1.1 線(xiàn)程類(lèi)型 Visual C++中線(xiàn)程分為工作者線(xiàn)程(Worker Thread)和用戶(hù)界面線(xiàn)程(User Interface Thread)兩大類(lèi)。 用戶(hù)界面線(xiàn)程的特點(diǎn)是擁有單獨的消息隊列,可以具有自己的窗口界面,能夠對事件和用戶(hù)輸入做出響應,具體實(shí)現時(shí)由CWinThread派生出一個(gè)類(lèi)。但其缺點(diǎn)是當需要停止或撤銷(xiāo)當前正在運行的線(xiàn)程而向其發(fā)送中止消息后,只有在消息隊列中排在前面的消息被一一處理完之后,線(xiàn)程才能接受中止消息并停止當前工作,這對CPU是一種浪費,在對實(shí)時(shí)性要求較高的工控程序中是不可容忍的。 工作者線(xiàn)程適用于處理后臺任務(wù),而不影響用戶(hù)對應用程序的使用。工作者線(xiàn)程僅僅由一個(gè)函數體實(shí)現,其實(shí)現簡(jiǎn)單,便于編程者控制,與事件同步方法相配合能對中止消息做出較快反應。 1.2 同步方法 在多線(xiàn)程應用程序中,兩個(gè)或更多的線(xiàn)程同時(shí)訪(fǎng)問(wèn)相同數據會(huì )導致不可預知的結果,因此保持線(xiàn)程間的同步是一個(gè)不可或缺的環(huán)節。VisualC++提供了四種同步方法:臨界區(Critical Section)、信號燈(Semaphore)、互斥量(Mutex)和事件(Event)。 其中采用臨界區、信號燈或互斥量進(jìn)行同步時(shí),線(xiàn)程間的同步過(guò)程由操作系統完全控制,系統僅僅防止多個(gè)線(xiàn)程對同一資源的同時(shí)使用,而相同優(yōu)先級的線(xiàn)程對同一資源的使用順序是編程者無(wú)法控制的。而在一般工控系統中,當主控臺下方設備數據變化時(shí),應能及時(shí)中止當前的計算(如果當前計算未完成的話(huà))并根據新的數據開(kāi)始新一輪的計算,因而要求各線(xiàn)程對所處理的數據有一定的操作次序。 事件同步是通過(guò)將事件自身設置為有信號或無(wú)信號來(lái)通知其它線(xiàn)程某一操作已完成或尚未完成,其設置可由編程人員手工完成,適合于工控程序應用。盡管事件同步方式平均效率比上面三種方式稍低,但在工控程序應用中相對于因數據未能及時(shí)更新而導致大量的無(wú)用計算及其對實(shí)時(shí)性的損害來(lái)講,還是非常值得的。 下面介紹的是筆者參與某‘九五’預研項目中所設計的主控臺程序的基本框架,這個(gè)程序框架應能適用于大多數工控系統的主控程序。 2 軟件框架 一般工控系統的主控部分通常所必須完成的兩件事是 與之相對應,該軟件具有一個(gè)主線(xiàn)程和兩個(gè)子線(xiàn)程,其中一個(gè)子線(xiàn)程為通信線(xiàn)程,另一個(gè)為計算線(xiàn)程。主線(xiàn)程是Windows下每個(gè)應用程序都具備的,負責線(xiàn)程間的同步、向計算線(xiàn)程和通信線(xiàn)程傳遞參數、管理人機界面、接收用戶(hù)輸入、數據庫的操作和管理等功能。通信線(xiàn)程通過(guò)通信端口(可以是串口、并口或網(wǎng)絡(luò )接口等)負責與下端的設備進(jìn)行通信并交換數據,當存在多級控制結構時(shí),還可用來(lái)與更高一級的控制設備進(jìn)行通信并向上傳遞數據。計算線(xiàn)程負責核心算法的實(shí)現,根據系統的不同完成不同的數據處理任務(wù)。程序結構如圖1。 進(jìn)程開(kāi)始后先由主線(xiàn)程建立通信線(xiàn)程與計算線(xiàn)程。通信線(xiàn)程監視通信端口,當下方設備發(fā)來(lái)數據時(shí),就向主線(xiàn)程發(fā)送自定義的WM_USER_COMM_NOTIFY消息,通知主線(xiàn)程計算數據有所改變,主線(xiàn)程則對之進(jìn)行處理,即中止當前的計算,并重新開(kāi)始計算。 3 具體實(shí)現 用Visual C++的AppWizard生成一個(gè)應用程序,這是主控程序的雛形,該應用程序暫取名為CtrSys,后面程序名都以此為準。 3.1 多線(xiàn)程的定義及生成 3.1.1 多線(xiàn)程的定義 向項目中加入threads.cpp文件,在該文件中寫(xiě)入通信線(xiàn)程和計算線(xiàn)程的控制函數。 控制函數有下面的原型: UINT MyThreadProc(LPVOID lpvThreadParam); lpvThreadParam參數是32位的值,這個(gè)值就是在線(xiàn)程對象產(chǎn)生時(shí)傳遞給線(xiàn)程構造函數的參數?刂坪瘮的芙忉尨酥档牟煌憩F方式。它可以被當作一個(gè)普通變量對待,也可以被視為一個(gè)指向包含有多個(gè)參數的結構指針,也可以被忽略。如果參數指向一個(gè)結構,這個(gè)結構可能不僅僅用來(lái)從調用者傳遞參數給線(xiàn)程,還可能用來(lái)從線(xiàn)程回傳數據給調用者。如果使用這樣的結構回傳給調用者,結果準備好后線(xiàn)程需要通知調用者。 控制函數終止時(shí),應該返回一個(gè)UINT類(lèi)型的值,表明終止的原因。返回碼0表示成功,其它值表示不同類(lèi)型的錯誤,這完全依賴(lài)實(shí)現情況。 按一般程序示例,線(xiàn)程通常在視類(lèi)或框架窗口類(lèi)中產(chǎn)生。但在工控程序中,通信與計算線(xiàn)程常常要大量地對計算數據進(jìn)行操作,根據文檔/視的程序框架結構,文檔類(lèi)常常用來(lái)存儲所要處理的數據。因此把計算與通信線(xiàn)程放在文檔類(lèi)中產(chǎn)生,并把產(chǎn)生線(xiàn)程的當前文檔對象的指針作為線(xiàn)程控制函數的參數傳遞給線(xiàn)程。 從而,在控制函數(CalcThreadProc ()和CommThreadProc())一開(kāi)始,就要對所傳來(lái)的參數進(jìn)行識別: CCtrsysDoc* pDoc = (CCtrsysDoc*)pParam; 注意要在文件開(kāi)頭包括進(jìn)文檔類(lèi)的頭文件 #include ″CtrsysDoc.h″ 3.1.2 多線(xiàn)程的產(chǎn)生 在文檔類(lèi)的構造函數中產(chǎn)生線(xiàn)程。程序啟動(dòng)時(shí)生成文檔對象,同時(shí)啟動(dòng)兩個(gè)線(xiàn)程。 //////////////////////////////////// // CCtrsysDoc construction/destruction CCtrsysDoc::CCtrsysDoc() { …… m_pCalcThread=AfxBeginThread(CalcThreadProc, this); m_pCommThread=AfxBeginThread(CommThreadProc, this); } 注意不要用Win32的CreateThread()建立線(xiàn)程,而應該用AfxBeginThread()函數,否則所建立的線(xiàn)程不能訪(fǎng)問(wèn)其它MFC對象。 3.2 線(xiàn)程間的同步 程序中設置有八個(gè)事件用于線(xiàn)程同步: HANDLE m_hEventPost; //用來(lái)允許通信線(xiàn)程向主框架 發(fā)送WM_USER_COMM_NOTIFY消息 HANDLE m_hEventStartCalc; //主框架通知計算線(xiàn)程開(kāi)始計算 HANDLE m_hEventCalcStarted; //計算線(xiàn)程通知主框架計算已經(jīng)開(kāi)始 HANDLE m_hEventStopCalc; //主框架通知計算線(xiàn)程中止計算 HANDLE m_hEventCalcStopped; //計算線(xiàn)程通知主框架計算已經(jīng)中止 HANDLE m_hEventCalcDone; //計算線(xiàn)程通知主框架計算已經(jīng)結束 HANDLE m_hEventUpdateSourceData; //主框架通知計算線(xiàn)程更新數據 HANDLE m_hEventSourceDataUpdated; //通信線(xiàn)程通知主框架數據已更新完畢 這八個(gè)事件是主線(xiàn)程和兩個(gè)子線(xiàn)程之間同步所必需的,讀者可根據自己程序的需要另行添加。 因各線(xiàn)程都以文檔對象指針為參數,這些事件都在文檔類(lèi)頭文件中定義,這些事件在文檔類(lèi)的構造函數中生成并賦初值。 CCtrsysDoc::CCtrsysDoc() { …… m_hEventPost=CreateEvent(NULL,TRUE,TRUE, NULL); m_hEventCalcDone=CreateEvent(NULL,TRUE,FALSE, NULL); m_hEventCalcStarted=CreateEvent(NULL,TRUE,FALSE,NULL); m_hEventStartCalc=CreateEvent(NULL, TRUE,FALSE, NULL); m_hEventSourceDataUpdated=CreateEvent(NULL,TRUE,FALSE, NULL); m_hEventUpdateSourceData=CreateEvent(NULL,TRUE,FALSE, NULL); m_hEventCalcStopped=CreateEvent(NULL,TRUE,FALSE, NULL); m_hEventStopCalc=CreateEvent(NULL, TRUE,FALSE, NULL); …… } 線(xiàn)程的同步工作主要在主框架CMainFrame類(lèi)的WM_USER_COMM_NOTIFY消息響應函數OnCommNotify中進(jìn)行。當下方通信設備參數改變時(shí),通信線(xiàn)程發(fā)送給CMainFrame類(lèi)一個(gè)WM_USER_COMM_NOTIFY消息。CMainFrame類(lèi)接收到消息后,在消息響應函數OnCommNotify中終止計算線(xiàn)程的當前計算,計算成功終止后由通信線(xiàn)程更新計算所需的數據源,待更新完畢后,重新開(kāi)始計算。線(xiàn)程同步部分流程如圖2。 3.3 通信線(xiàn)程 通信線(xiàn)程部分流程如圖3所示。 3.4 計算線(xiàn)程 編程者應根據數據處理過(guò)程,在運算量較大或循環(huán)次數較多的地方設置對m_hEventStopCalc事件的查詢(xún)。當數據發(fā)生更新時(shí),使用其它線(xiàn)程類(lèi)型和同步方法往往必須等到數據處理部分結束,這樣整個(gè)一次數據處理都是無(wú)用計算;而采用上述方法,因數據更新所造成的無(wú)用計算僅僅是一步循環(huán)或幾行指令,相比而言,所導致的延時(shí)和CPU浪費是微不足道的。 計算線(xiàn)程部分流程如圖4所示。 |
大體的流程知道了,能再講詳細點(diǎn)就好了,學(xué)習了,謝謝樓主分享! |
謝謝學(xué)習了。。。。。。 |
O(∩_∩)O謝謝 |
O(∩_∩)O謝謝 |
O(∩_∩)O謝謝 |