單體強(qiáng),陳 雷,張萬(wàn)發(fā)
(軍械工程學(xué)院 彈藥工程系,河北 石家莊 050003)
隨著現(xiàn)代測(cè)控系統(tǒng)自動(dòng)化水平的不斷提高,在大型測(cè)控系統(tǒng)的軟件設(shè)計(jì)過(guò)程中,經(jīng)常會(huì)遇到需要多個(gè)任務(wù)同時(shí)工作的情況。若采用傳統(tǒng)的單線程編程方法,軟件系統(tǒng)的執(zhí)行效率低,系統(tǒng)可靠性差;而多線程技術(shù)特別適用于實(shí)時(shí)多任務(wù)系統(tǒng)[1],采用多線程技術(shù),則可以把這些任務(wù)分配給多個(gè)線程同時(shí)執(zhí)行,在一段時(shí)間內(nèi)并行完成多個(gè)任務(wù),提高了軟件的執(zhí)行效率和系統(tǒng)可靠性。LabWindows/CVI是NI公司提供的一種ANSI C集成開發(fā)環(huán)境[2],其將多線程技術(shù)引入虛擬儀器軟件設(shè)計(jì)中,有效利用OS和CPU,縮短響應(yīng)時(shí)間、避免擁塞出現(xiàn),使系統(tǒng)整體性能得到大幅提高。
進(jìn)程通常被定義為應(yīng)用程序的運(yùn)行實(shí)例。在Win32中,每個(gè)進(jìn)程擁有4 GB的地址空間,此外還有別的資源,如文件、動(dòng)態(tài)內(nèi)存分配和線程[3]。這些資源在進(jìn)程中被創(chuàng)建,當(dāng)進(jìn)程終止時(shí)被釋放。
線程是進(jìn)程內(nèi)部可獨(dú)立執(zhí)行單元,是操作系統(tǒng)對(duì)系統(tǒng)資源的基本調(diào)度單位。多線程是指操作系統(tǒng)支持一個(gè)進(jìn)程中執(zhí)行多個(gè)線程的能力。應(yīng)用程序中的每個(gè)進(jìn)程都擁有一個(gè)主線程和零到多個(gè)次線程。同屬于一個(gè)進(jìn)程的線程共享進(jìn)程的虛擬地址空間,線程之間可共享進(jìn)程的全局?jǐn)?shù)據(jù)和資源。此外,每個(gè)線程都單獨(dú)保存一些數(shù)據(jù)結(jié)構(gòu)、CPU寄存器指針和堆棧,以保存線程和執(zhí)行環(huán)境。進(jìn)程與線程之間的資源分布關(guān)系如圖1所示。
圖1 進(jìn)程與線程之間資源分布關(guān)系圖Fig.1 Relation chart of thread and process resources
LabWindows/CVI提供兩種次線程運(yùn)行代碼的高級(jí)機(jī)制,分別是線程池 (thread pools)和異步定時(shí)器(asynchronous timers)。線程池適用于需要不連續(xù)地執(zhí)行或在循環(huán)中執(zhí)行的任務(wù),而異步定時(shí)器適用于在固定時(shí)間間隔內(nèi)執(zhí)行的任務(wù)[4]。
線程在線程池中創(chuàng)建和管理,線程池在主線程中創(chuàng)建。當(dāng)喚出次線程的控件得到響應(yīng)時(shí),則在線程池中創(chuàng)建一個(gè)次線程,線程函數(shù)就通過(guò)占用Windows的時(shí)間片開始運(yùn)行,而不再需要程序流程的干預(yù),直到主動(dòng)或被動(dòng)停止為止。線程池中對(duì)多線程的管理包括創(chuàng)建線程池、創(chuàng)建線程、運(yùn)行線程函數(shù)、釋放線程池資源等。
線程池的運(yùn)行需要調(diào)用Utility Library中的Cmt Schedule Thread Pool Function函數(shù),把在次線程中執(zhí)行的函數(shù)名傳遞給它,使其在線程池的一個(gè)次線程中運(yùn)行。調(diào)用Cmt Waitfor Schedule Thread P-ool Function Completion函數(shù)使主線程在結(jié)束之前處于等待狀態(tài),直到線程函數(shù)完成執(zhí)行。如果主線程在次線程完成執(zhí)行之前退出,則次線程可能沒(méi)有機(jī)會(huì)完全釋放其占用的資源,次線程中用到的庫(kù)函數(shù)也可能沒(méi)有機(jī)會(huì)完全釋放。在多線程程序中,調(diào)用函數(shù)Cmt Release Thread Pool Function ID和函數(shù)Cmt Terminate Thread Pool Thread均可退出線程,但是后者無(wú)法保證安全退出線程,處理不當(dāng)有可能導(dǎo)致無(wú)法工作。調(diào)用函數(shù)Cmt New Thread Pool可以創(chuàng)建一個(gè)新的線程池,Cmt New Thread Pool會(huì)返回線程池的句柄(pool Handle),并把它傳遞給函數(shù)CmtSchedule Thread Pool Function的第一個(gè)參數(shù)。通過(guò)Cmt New Thread Pool創(chuàng)建的線程池,在該線程結(jié)束時(shí),必須調(diào)用Cmt Discard Thread Pool函數(shù)釋放該線程池的資源。
同步定時(shí)器執(zhí)行線程的優(yōu)先級(jí)比較低[5],當(dāng)程序線程在別處停滯或響應(yīng)用戶界面操作時(shí),其發(fā)送的定時(shí)消息會(huì)受到消息隊(duì)列和系統(tǒng)時(shí)鐘頻率等因素的影響,使定時(shí)消息得不到及時(shí)的響應(yīng)和處理,這給基于定時(shí)功能的重要操作的編程帶來(lái)極大的不利。異步定時(shí)器利用Windows多媒體定時(shí)器在指定的時(shí)間間隔點(diǎn)調(diào)用執(zhí)行函數(shù),異步定時(shí)的時(shí)間間隔精確,可對(duì)設(shè)備進(jìn)行軟件定時(shí)。與同步定時(shí)器相比,異步定時(shí)器使用獨(dú)立線程,與程序主線程無(wú)關(guān),能夠提供可靠的定時(shí)精度,有效避免了由于程序主線程或用戶界面操作而產(chǎn)生的延時(shí),確保了定時(shí)器事件的實(shí)時(shí)性。異步定時(shí)器本質(zhì)上是多線程技術(shù)的一種應(yīng)用形式。
基于LabWindows/CVI,利用同步定時(shí)器實(shí)現(xiàn)單線程工作模式,如圖2所示,利用異步定時(shí)器實(shí)現(xiàn)多線程工作模式,如圖3所示,以此比較兩者的軟件執(zhí)行效率和系統(tǒng)可靠性。單線程模式下,在定時(shí)器控件的回調(diào)函數(shù)中產(chǎn)生數(shù)據(jù)并在第一個(gè)繪圖控件上繪出曲線。多線程模式下,利用異步時(shí)鐘產(chǎn)生一個(gè)次線程,并在這個(gè)線程中產(chǎn)生數(shù)據(jù),同時(shí)在主線程的定時(shí)器控件的回調(diào)函數(shù)中顯示數(shù)據(jù)。第二個(gè)繪圖控件用來(lái)直觀反映定時(shí)器控件每次執(zhí)行代碼所需的時(shí)間。在單線程工作模式下,若進(jìn)行其他界面操作,那么顯示數(shù)據(jù)操作被推遲,顯示的數(shù)據(jù)失真,時(shí)間間隔大大超過(guò)了所界定的200 ms。在多線程工作模式下,因?yàn)轱@示數(shù)據(jù)和產(chǎn)生數(shù)據(jù)分屬兩個(gè)不同的線程,所以其它界面操作并未影響數(shù)據(jù)處理與波形顯示產(chǎn)生。這說(shuō)明多線程在提高軟件執(zhí)行效率和系統(tǒng)可靠性等方面均優(yōu)于單線程。
圖2 單線程工作模式Fig.2 Single threading modle
圖3 多線程工作模式Fig.3 Multithreading modle
數(shù)據(jù)保護(hù)是使用多線程技術(shù)時(shí)必須解決的一個(gè)關(guān)鍵問(wèn)題,保護(hù)全局變量、靜態(tài)局部變量和動(dòng)態(tài)分配的變量,避免它們被多個(gè)線程同時(shí)訪問(wèn)時(shí)造成邏輯錯(cuò)誤。 Lab Windows/CVI提供了3種線程同步數(shù)據(jù)保護(hù)機(jī)制:線程鎖(thread lock)、線程安全變量 (thread safe variables)和線程安全隊(duì)列(thread safe queues)[6]。線程鎖與線程安全變量數(shù)據(jù)保護(hù)機(jī)制是線程同步中對(duì)資源的互斥使用;線程安全隊(duì)列數(shù)據(jù)保護(hù)機(jī)制是線程同步中兩個(gè)以上線程基于某個(gè)條件來(lái)協(xié)調(diào)它們的活動(dòng),一個(gè)線程的執(zhí)行依賴于另一個(gè)協(xié)作線程的消息或信號(hào),當(dāng)一個(gè)線程沒(méi)有得到來(lái)自另一個(gè)線程的消息或信號(hào)時(shí)則需等待,直到消息或信號(hào)到達(dá)才被喚醒。
在線程鎖數(shù)據(jù)保護(hù)機(jī)制中,數(shù)據(jù)保護(hù)是通過(guò)將保存數(shù)據(jù)的變量和OS中的線程鎖對(duì)象進(jìn)行關(guān)聯(lián)來(lái)實(shí)現(xiàn)的。在一個(gè)特定的時(shí)間內(nèi),OS只允許一個(gè)線程獲得特定的線程鎖對(duì)象。程序利用CmtNewLock函數(shù)建立線程鎖;利用CmtGetLock函數(shù)使指定線程獲得線程鎖控制權(quán)。每一個(gè)時(shí)刻只能有一個(gè)線程獲得線程鎖,且每次調(diào)用此函數(shù)后必須調(diào)用CmtReleaseLock函數(shù)釋放線程鎖控制權(quán)。若程序中使用的線程占有線程鎖較長(zhǎng)時(shí)間,可能導(dǎo)致其它想獲得線程鎖的線程不得不等待較長(zhǎng)時(shí)間,降低了程序的可執(zhí)行性,容易出現(xiàn)阻塞甚至死鎖,效率較低。
線程安全變量把數(shù)據(jù)和OS中的線程鎖定對(duì)象組合成為一個(gè)整體。因?yàn)橹恍枰獋鬟f線程安全變量句柄,而不是傳遞線程鎖句柄和被保護(hù)的變量,所以被保護(hù)數(shù)據(jù)更容易在函數(shù)間傳遞。第一次訪問(wèn)線程安全變量需調(diào)用Initialize Var Name函數(shù),在程序中止前調(diào)用Uninitialize Var Name函數(shù),調(diào)用Cet Pointer To Var Name函數(shù)來(lái)獨(dú)占該變量,可以進(jìn)行線程安全變量的訪問(wèn)。訪問(wèn)結(jié)束后,調(diào)用Release Pointer To Var Name函數(shù)放棄對(duì)該變量的占有權(quán),以便其它線程訪問(wèn)該變量。
線程安全隊(duì)列是線程間安全傳輸數(shù)組的一種機(jī)制,是為多個(gè)線程之間傳遞大批量數(shù)據(jù)提供的一種數(shù)據(jù)保護(hù)措施。線程安全隊(duì)列內(nèi)部采用了鎖策略,有效避免了線程間的沖突、數(shù)據(jù)錯(cuò)誤、死鎖等情況的出現(xiàn)。調(diào)用Cmt New TSQ函數(shù)可以實(shí)現(xiàn)線程安全隊(duì)列的建立。在調(diào)用該函數(shù)時(shí),根據(jù)線程間傳送數(shù)據(jù)信息量多少,指定線程安全隊(duì)列的項(xiàng)目數(shù)和每一項(xiàng)目的大小。寫線程調(diào)用函數(shù)Cmt Write TSQ Data將一定數(shù)量的數(shù)據(jù)信息寫入到線程安全隊(duì)列中,而讀線程調(diào)用函數(shù)Cmt Read TSQ Data從安全隊(duì)列中讀取指定數(shù)量的數(shù)據(jù)信息。
某武器系統(tǒng)中所涉及的需進(jìn)行測(cè)控的資源達(dá)二十多種。所有測(cè)控資源的初始化都是在啟動(dòng)測(cè)控程序時(shí)完成的,所有測(cè)控資源的關(guān)閉是在退出程序時(shí)完成的。因此,若用單線程初始化/關(guān)閉所有的測(cè)控資源會(huì)導(dǎo)致程序啟動(dòng)/退出時(shí)間過(guò)長(zhǎng),降低了軟件執(zhí)行效率和系統(tǒng)可靠性。據(jù)此,在設(shè)計(jì)啟動(dòng)/退出程序時(shí),采用了Lab Windows/CVI多線程技術(shù)。限于篇幅,只給出測(cè)控程序的啟動(dòng)流程圖,如圖4所示。
程序啟動(dòng)后開始執(zhí)行主線程,主線程啟動(dòng)兩個(gè)次線程來(lái)初始化儀器。當(dāng)次線程初始化儀器完畢后,置該次線程的標(biāo)志變量為1,然后調(diào)用函數(shù)Set Sleep Policy,使其處于休眠狀態(tài)。在退出程序時(shí),再啟動(dòng)這兩個(gè)次線程關(guān)閉測(cè)控資源。主線程用于處理用戶界面事件,若用戶界面上的“取消”控件被按下,則應(yīng)立即終止測(cè)控資源的初始化,退出測(cè)控程序。如果用單線程編寫測(cè)控程序,當(dāng)出現(xiàn)突發(fā)故障需立即終止程序執(zhí)行時(shí),按下“終止”控件,有時(shí)會(huì)發(fā)現(xiàn)程序不能立即響應(yīng)用戶界面事件,可能由此造成嚴(yán)重后果。采用多線程技術(shù)可以很好的解決此問(wèn)題。在“啟動(dòng)”控件按下時(shí),系統(tǒng)創(chuàng)建一個(gè)新的線程用于自動(dòng)測(cè)控過(guò)程,而主線程用于處理用戶界面事件,這樣在需要緊急終止測(cè)試程序時(shí),可以保證事件響應(yīng)的實(shí)時(shí)性。當(dāng)主線程接到終止命令時(shí),調(diào)用Cmt Terminate Thread Pool Thread函數(shù)結(jié)束次線程函數(shù)的執(zhí)行,并調(diào)用函數(shù)Cmt Release Thread Pool Function ID釋放線程函數(shù)資源,提高了測(cè)控系統(tǒng)的可靠性。
圖4 測(cè)控程序啟動(dòng)過(guò)程流程圖Fig.4 Flow chart of the software startup
深入研究了Lab Windows/CVI多線程技術(shù)的運(yùn)行機(jī)制及其數(shù)據(jù)保護(hù)機(jī)制,通過(guò)分析和實(shí)際應(yīng)用表明,該技術(shù)提高了操作系統(tǒng)的執(zhí)行效率和系統(tǒng)的可靠性,在改善系統(tǒng)實(shí)時(shí)性和最大程度利用多處理器的性能上具有明顯的優(yōu)勢(shì),對(duì)測(cè)控技術(shù)的發(fā)展具有重要的作用。
[1]張毅剛.虛擬儀器軟件開發(fā)環(huán)境Labwindows/CVI編程指南[M].北京:機(jī)械工業(yè)出版社,2002.
[2]陳矯陽(yáng),陳楸.基于LabWindows/CVI多線程數(shù)據(jù)采集的研究[J].科學(xué)技術(shù)與工程,2008,8(9):2459-2461.CHEN Jiao-yang,CHEN Qiu.Study of multithread data acquiring on the base of LabWindows/CVI[J].Science Technology and Engineering,2008,8(9):2459-2461.
[3]成鳳敏,蘇小光.多線程技術(shù)在虛擬儀器軟件開發(fā)中的應(yīng)用[J].中國(guó)測(cè)試技術(shù),2008,34(2):48-50.CHENG Feng-min,SU Xiao-guang.Application of multithreading in virtual instrument software development[J].China Measurement Technology,2008,34(2):48-50.
[4]楊東升,王高峰.多線程技術(shù)在虛擬儀器開發(fā)軟件LabWindows/CVI中的實(shí)現(xiàn)[J].電測(cè)與儀表,2005,42(471):39-54.YANG Dong-sheng,WANG Gao-feng.Application of multithreading based on virtual instrument software Lab Windows/CVI[J].Electrical Measurement&Instrumentation,2005,42(471):39-54.
[5]姜守達(dá),吳昌盛,孫震.LabWindows/CVI多線程機(jī)制在數(shù)據(jù)采集中的應(yīng)用[J].計(jì)算機(jī)應(yīng)用,2004,23(8):56-63.JIANG Shou-da,WU Chang-sheng,SUN Zhen.Application of multithreading in data acquisition based on LabWindows/CVI[J].Computer Application,2004,23(8):56-63.
[6]馬青亮,周倫彬,鮑芳.LabWindows/CVI多線程機(jī)制在虛擬數(shù)字存儲(chǔ)示波器中的應(yīng)用[J].中國(guó)測(cè)試技術(shù),2007,34(1):60-62.MA Qing-liang,ZHOU Lun-bin,BAO Fang.Application of Labwindows/CVI multithreading technology in virtual DSO[J].China Measurement Technology,2007,34(1):60-62.