色婷婷AⅤ一区二区三区|亚洲精品第一国产综合亚AV|久久精品官方网视频|日本28视频香蕉

          新聞中心

          EEPW首頁 > 測試測量 > 設(shè)計(jì)應(yīng)用 > LabWindows?/CVI中的多線程技術(shù)

          LabWindows?/CVI中的多線程技術(shù)

          作者: 時間:2016-12-23 來源:網(wǎng)絡(luò) 收藏

          1. 進(jìn)行多線程編程的原因

          在程序中使用多線程技術(shù)的原因主要有四個。最常見的原因是多個任務(wù)進(jìn)行分割,這些任務(wù)中的一個或多個是對時間要求嚴(yán)格的而且易被其他任務(wù)的運(yùn)行所干涉。例如,進(jìn)行數(shù)據(jù)采集并顯示用戶界面的程序就很適合使用多線程技術(shù)實(shí)現(xiàn)。在這種類型的程序中,數(shù)據(jù)采集是時間要求嚴(yán)格的任務(wù),它很可能被用戶界面的任務(wù)打斷。在LabWindows/CVI程序中使用單線程方法時,程序員可能需要從數(shù)據(jù)采集緩沖區(qū)讀出數(shù)據(jù)并將它們顯示到用戶界面的曲線上,然后處理事件對用戶界面進(jìn)行更新。當(dāng)用戶在界面上進(jìn)行操作(如在圖表上拖動光標(biāo))時,線程將繼續(xù)處理用戶界面事件而不能返回到數(shù)據(jù)采集任務(wù),這將導(dǎo)致數(shù)據(jù)采集緩沖區(qū)的溢出。而在LabWindows/CVI程序中使用多線程技術(shù)時,程序員可以將數(shù)據(jù)采集操作放在一個線程中,而將用戶界面處理放在另一個線程中。這樣,在用戶對界面進(jìn)行操作時,操作系統(tǒng)將進(jìn)行線程切換,為數(shù)據(jù)采集線程提供完成任務(wù)所需的時間。

          本文引用地址:http://cafeforensic.com/article/201612/333285.htm

          在程序中使用多線程技術(shù)的第二個原因是程序中可能需要同時進(jìn)行低速的輸入/輸出操作。例如,使用儀器來測試電路板的程序?qū)亩嗑€程技術(shù)中獲得顯著的性能提升。在LabWindows/CVI程序中使用單線程技術(shù)時,程序員需要從串口發(fā)送數(shù)據(jù),初始化電路板。,程序需要等待電路板完成操作之后,再去初始化測試儀器。必須要等待測試儀器完成初始化之后,再進(jìn)行測量,。在LabWindows/CVI程序中使用多線程技術(shù)時,你可以使用另一個線程來初始化測試儀器。這樣,在等待電路板初始化的同時等待儀器初始化。低速的輸入/輸出操作同時進(jìn)行,減少了等待所需要的時間總開銷。

          在程序中使用多線程技術(shù)的第三個原因是借助多處理器計(jì)算機(jī)來提高性能。計(jì)算機(jī)上的每個處理器可以都執(zhí)行一個線程。這樣,在單處理器計(jì)算機(jī)上,操作系統(tǒng)只是使多個線程看起來是同時執(zhí)行的,而在多處理器計(jì)算機(jī)上,操作系統(tǒng)才是真正意義上同時執(zhí)行多個線程的。例如,進(jìn)行數(shù)據(jù)采集、將數(shù)據(jù)寫入磁盤、分析數(shù)據(jù)并且在用戶界面上顯示分析數(shù)據(jù),這樣的程序很可能通過多線程技術(shù)和多處理器計(jì)算機(jī)運(yùn)行得到性能提升。將數(shù)據(jù)寫到磁盤上和分析用于顯示的數(shù)據(jù)是可以同時執(zhí)行的任務(wù)。

          在程序中使用多線程技術(shù)的第四個原因是在多個環(huán)境中同時執(zhí)行特定的任務(wù)。例如,程序員可以在應(yīng)用程序中利用多線程技術(shù)在測試艙進(jìn)行并行化測試。使用單線程技術(shù),應(yīng)用程序需要動態(tài)分配空間來保存每個艙中的測試結(jié)果。應(yīng)用程序需要手動維護(hù)每個記錄及其對應(yīng)的測試艙的關(guān)系。而使用多線程技術(shù),應(yīng)用程序可以創(chuàng)建獨(dú)立的線程來處理每個測試艙。然后,應(yīng)用程序可以使用線程局部變量為每個線程創(chuàng)建測試結(jié)果記錄。測試艙與結(jié)果記錄間的關(guān)系是自動維護(hù)的,使應(yīng)用程序代碼得以簡化。

          2. 選擇合適的操作系統(tǒng)

          微軟公司的Windows 9x系列操作系統(tǒng)不支持多處理器計(jì)算機(jī)。所以,你必須在多處理器計(jì)算機(jī)上運(yùn)行Windows Vista/XP/2000/NT 4.0系統(tǒng)來享受多處理器帶來的好處。而且,即使在單處理器計(jì)算機(jī)上,多線程程序在Windows Vista/XP/2000/NT 4.0上的性能也比在Windows 9x上好。這要?dú)w功于Windows Vista/XP/2000/NT 4.0系統(tǒng)有著更為高效的線程切換技術(shù)。但是,這種性能上的差別在多數(shù)多線程程序中體現(xiàn)得并不是十分明顯。

          對于程序開發(fā),特別是編寫和調(diào)試多線程程序而言,Windows Vista/XP/2000/NT 4.0系列操作系統(tǒng)比Windows 9x系列更為穩(wěn)定,當(dāng)運(yùn)行操作系統(tǒng)代碼的線程被暫停或終止的時候,操作系統(tǒng)的一些部分有可能出于不良狀態(tài)中。這種情況使得Windows 9x操作系統(tǒng)崩潰的幾率遠(yuǎn)遠(yuǎn)高于Windows Vista/XP/2000/NT 4.0系統(tǒng)的幾率。所以,NI公司推薦用戶使用運(yùn)行Windows Vista/XP/2000/NT 4.0操作系統(tǒng)的計(jì)算機(jī)來開發(fā)多線程程序。

          3. LabWindows/CVI中的多線程技術(shù)簡介

          NI LabWindows/CVI軟件自二十世紀(jì)九十年代中期誕生之日起就支持多線程應(yīng)用程序的創(chuàng)建。現(xiàn)在,隨著多核CPU的廣泛普及,用戶可以使用LabWindows/CVI來充分利用多線程技術(shù)的優(yōu)勢。

          與Windows SDK threading API(Windows 軟件開發(fā)工具包線程API)相比,LabWindows/CVI的多線程庫提供了以下多個性能優(yōu)化:

          • Thread pools幫助用戶將函數(shù)調(diào)度到獨(dú)立的線程中執(zhí)行。Thread pools處理線程緩存來最小化與創(chuàng)建和銷毀線程相關(guān)的開銷。
          • Thread-safe queues對線程間的數(shù)據(jù)傳遞進(jìn)行了抽象。一個線程可以在另一個線程向隊(duì)列寫入數(shù)據(jù)的同時,從隊(duì)列中讀取數(shù)據(jù)。
          • Thread-safe variables高效地將臨界代碼段和任意的數(shù)據(jù)類型結(jié)合在一起。用戶可以調(diào)用簡單的函數(shù)來獲取臨界代碼段,設(shè)定變量值,然后釋放臨界代碼段。
          • Thread locks提供了一致的API并在必要時自動選擇合適的機(jī)制來簡化臨界代碼段和互斥量的使用。例如,如果需要在進(jìn)程間共享互斥鎖,或者線程需要在等待鎖的時候處理消息,LabWindows/CVI會自動使用互斥量。臨界代碼段使用在其它場合中,因?yàn)樗痈咝А?/li>
          • Thread-local variables為每個線程提供變量實(shí)例。操作系統(tǒng)對每個進(jìn)程可用的線程局部變量的數(shù)量進(jìn)行了限制。LabWindows/CVI在實(shí)現(xiàn)過程中對線程局部變量進(jìn)行了加強(qiáng),程序中的所有線程局部變量只使用一個進(jìn)程變量。

          可以在Utility Library»Multithreading下的LabWindows/CVI庫函數(shù)樹狀圖中找到所有的多線程函數(shù)。

          4. 在LabWindows/CVI的輔助線程中運(yùn)行代碼

          單線程程序中的線程被稱為主線程。在用戶告訴操作系統(tǒng)開始執(zhí)行特定的程序時,操作系統(tǒng)將創(chuàng)建主線程。在多線程程序中,除了主線程外,程序還通知操作系統(tǒng)創(chuàng)建其他的線程。這些線程被稱為輔助線程。主線程和輔助線程的主要區(qū)別在于它們開始執(zhí)行的位置。操作系統(tǒng)從main或者WinMain函數(shù)開始執(zhí)行主線程,而由開發(fā)人員來指定輔助線程開始執(zhí)行的位置。

          在典型的LabWindows/CVI多線程程序中,開發(fā)者使用主線程來創(chuàng)建、顯示和運(yùn)行用戶界面,而使用輔助線程來進(jìn)行其它時間要求嚴(yán)格的操作,如數(shù)據(jù)采集等。LabWindows/CVI提供了兩種在輔助進(jìn)程中運(yùn)行代碼的高級機(jī)制。這兩種機(jī)制是線程池(thread pools)和異步定時器。線程池適合于執(zhí)行若干次的或者一個循環(huán)內(nèi)執(zhí)行的任務(wù)。而異步定時器適合于定期進(jìn)行的任務(wù)。

          使用線程池

          為了使用LabWindows/CVI的線程池在輔助線程中執(zhí)行代碼,需要調(diào)用Utility Library中的CmtScheduleThreadPoolFunction函數(shù)。將需要在輔助線程中運(yùn)行的函數(shù)名稱傳遞進(jìn)來。線程池將這個函數(shù)調(diào)度到某個線程中執(zhí)行。根據(jù)配置情況和當(dāng)前的狀態(tài),線程池可能會創(chuàng)建新的線程來執(zhí)行這個函數(shù)、也可能會使用已存在的空閑進(jìn)程執(zhí)行函數(shù)或者會等待一個活躍的線程變?yōu)榭臻e然后使用該線程執(zhí)行預(yù)定的函數(shù)。傳遞給CmtScheduleThreadPoolFunction的函數(shù)被稱為線程函數(shù)。線程池中的線程函數(shù)可以選擇任意的名稱,但是必須遵循以下原型:

          int CVICALLBACK ThreadFunction (void *functionData);

          下面的代碼顯示了如何使用CmtScheduleThreadPoolFunction函數(shù)在輔助進(jìn)程中執(zhí)行一個數(shù)據(jù)采集的線程。

          int CVICALLBACK DataAcqThreadFunction (void *functionData);
          int main(int argc, char *argv[])
          {
          int panelHandle;
          int functionId;

          if (InitCVIRTE (0, argv, 0) == 0)
          return -1; /* out of memory */
          if ((panelHandle = LoadPanel(0, "DAQDisplay.uir", PANEL)) < 0)
          return -1;
          DisplayPanel (panelHandle);

          CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE, DataAcqThreadFunction, NULL, &functionId);
          RunUserInterface ();
          DiscardPanel (panelHandle);
          CmtWaitForThreadPoolFunctionCompletion (DEFAULT_THREAD_POOL_HANDLE, functionId, 0);
          return 0;
          }
          int CVICALLBACK DataAcqThreadFunction (void *functionData)
          {
          while (!quit) {
          Acquire(. . .);
          Analyze(. . .);
          }
          return 0;
          }

          在前面的代碼中,主線程調(diào)用了CmtScheduleThreadPoolFunction函數(shù),使線程池創(chuàng)建了一個新的線程來運(yùn)行DataAcqThreadFunction線程函數(shù)。主線程從CmtScheduleThreadPoolFunction函數(shù)返回,而無須等待DataAcqThreadFunction函數(shù)完成。在輔助線程中的DataAcqThreadFunction函數(shù)與主線程中的調(diào)用是同時執(zhí)行的。

          CmtScheduleThreadPoolFunction函數(shù)的第一個參數(shù)表示用于進(jìn)行函數(shù)調(diào)度的線程池。LabWindows/CVI的Utility Library中包含了內(nèi)建的默認(rèn)線程池。傳遞常數(shù)DEFAULT_THREAD_POOL_HANDLE表示用戶希望使用默認(rèn)的線程池。但是用戶不能對默認(rèn)線程池的行為進(jìn)行自定義。用戶可以調(diào)用CmtNewThreadPool函數(shù)來創(chuàng)建自定義的線程池。CmtNewThreadPool函數(shù)返回一個線程池句柄,這個句柄將作為第一個參數(shù)傳遞給CmtScheduleThreadPoolFunction函數(shù)。程序員需要調(diào)用CmtDiscardThreadPool函數(shù)來釋放由CmtNewThreadPool函數(shù)創(chuàng)建的線程池資源。

          CmtScheduleThreadPoolFunction函數(shù)中的最后一個參數(shù)返回一個標(biāo)識符,用于在后面的函數(shù)調(diào)用中引用被調(diào)度的函數(shù)。調(diào)用CmtWaitForThreadPoolFunctionCompletion函數(shù)使得主線程等待線程池函數(shù)結(jié)束后再退出。如果主線程在輔助線程完成之前退出,那么可能會造成輔助線程不能正確地清理分配到的資源。這些輔助線程使用的庫也不會被正確的釋放掉。

          使用異步定時器

          為了使用LabWindows/CVI的異步定時器在輔助線程中運(yùn)行代碼,需要調(diào)用Toolslib中的NewAsyncTimer函數(shù)。需要向函數(shù)傳遞在輔助線程中運(yùn)行的函數(shù)名稱和函數(shù)執(zhí)行的時間間隔。傳遞給NewAsyncTimer的函數(shù)被稱為異步定時器回調(diào)函數(shù)。異步定時器儀器驅(qū)動程序會按照用戶指定的周期調(diào)用異步定時器回調(diào)函數(shù)。異步定時器回調(diào)函數(shù)的名稱是任意的,但是必須遵循下面的原型:

          int CVICALLBACKFunctionName(int reserved, int timerId, int event, void *callbackData, int eventData1, int eventData2);

          由于LabWindows/CVI的異步定時器儀器驅(qū)動使用Windows多媒體定時器來實(shí)現(xiàn)異步定時器回調(diào)函數(shù),所以用戶可指定的最小間隔是隨使用的計(jì)算機(jī)不同而變化的。如果用戶指定了一個比系統(tǒng)可用的最大分辨率還小的時間間隔,那么可能會產(chǎn)生不可預(yù)知的行為。不可預(yù)知的行為通常發(fā)生在設(shè)定的時間間隔小于10ms時。同時,異步定時器儀器驅(qū)動使用一個多媒體定時器線程來運(yùn)行單個程序中注冊的所有異步定時器回調(diào)函數(shù)。所以,如果用戶希望程序并行地執(zhí)行多個函數(shù),那么NI公司推薦使用LabWindows/CVI Utility Library中的線程池函數(shù)來代替異步定時器函數(shù)。

          5. 保護(hù)數(shù)據(jù)

          在使用輔助線程的時候,程序員需要解決的一個非常關(guān)鍵的問題是數(shù)據(jù)保護(hù)。在多個線程同時進(jìn)行訪問時,程序需要對全局變量、靜態(tài)局部變量和動態(tài)分配的變量進(jìn)行保護(hù)。不這樣做會導(dǎo)致間歇性的邏輯錯誤發(fā)生,而且很難發(fā)現(xiàn)。LabWindows/CVI提供了各種高級機(jī)制幫助用戶對受到并發(fā)訪問的數(shù)據(jù)進(jìn)行保護(hù)。保護(hù)數(shù)據(jù)時,一個重要的考慮就是避免死鎖。

          如果一個變量被多個線程訪問,那么它必須被保護(hù),以確保它的值可靠。例如下面一個例子,一個多線程程序在多個線程中對全局整型counter變量的值進(jìn)行累加。

          count = count + 1;

          這段代碼按照下列CPU指令順序執(zhí)行的:

          1.將變量值移入處理器的寄存器中

          2.增加寄存器中的變量值

          3.把寄存器中的變量值寫回count變量

          由于操作系統(tǒng)可能在線程運(yùn)行過程中的任意時刻打斷線程,所以執(zhí)行這些指令的兩個線程可能按照如下的順序進(jìn)行(假設(shè)count初始值為5):

          線程1:將count變量的值移到寄存器中。(count=5,寄存器=5),然后切換到線程2(count=5,寄存器未知)。

          線程2:將count變量的值移到寄存器中(count=5,寄存器=5)。

          線程2: 增加寄存器中的值(count=5,寄存器=6)。

          線程2: 將寄存器中的值寫回count變量(count=6,寄存器=6),然后切換回線程1.(count=6,寄存器=5)。

          線程1: 增加寄存器的值。(count=6,寄存器=6)。

          線程1: 將寄存器中的值寫回count變量(count= 6, register = 6)。

          由于線程1在增加變量值并將其寫回之前被打斷,所以變量count的值被設(shè)為6而不是7。操作系統(tǒng)為系統(tǒng)中地每一個線程的寄存器都保存了副本。即使編寫了count++這樣的代碼,用戶還是會遇到相同的問題,因?yàn)樘幚砥鲿⒋a按照多條指令執(zhí)行。注意,特定的時序狀態(tài)導(dǎo)致了這個錯誤。這就意味著程序可能正確運(yùn)行1000次,而只有一次故障。經(jīng)驗(yàn)告訴我們,有著數(shù)據(jù)保護(hù)不當(dāng)問題的多線程程序在測試的過程中通常是正確的,但是一到客戶安裝并運(yùn)行它們時,就會發(fā)生錯誤。

          需要保護(hù)的數(shù)據(jù)類型

          只有程序中的多個線程可以訪問到的數(shù)據(jù)是需要保護(hù)的。全局變量、靜態(tài)局部變量和動態(tài)分配內(nèi)存位于通常的內(nèi)存空間中,程序中的所有線程都可以訪問它們。多個線程對內(nèi)存空間中存儲的這些類型的數(shù)據(jù)進(jìn)行并發(fā)訪問時,必須加以保護(hù)。函數(shù)參數(shù)和非靜態(tài)局部變量位于堆棧上。操作系統(tǒng)為每個線程分配獨(dú)立的堆棧。因此,每個線程都擁有參數(shù)和非靜態(tài)局部變量的獨(dú)立副本,所以它們不需要為并發(fā)訪問進(jìn)行保護(hù)。下面的代碼顯示了必須為并發(fā)訪問而保護(hù)的數(shù)據(jù)類型。

          int globalArray[1000];// Must be protected
          static staticGlobalArray[500];// Must be protected
          int globalInt;// Must be protected

          void foo (int i)// i does NOT need to be protected
          {
          int localInt;// Does NOT need to be protected
          int localArray[1000];// Does NOT need to be protected
          int *dynamicallyAllocdArray;// Must be protected
          static int staticLocalArray[1000];// Must be protected

          dynamicallyAllocdArray = malloc (1000 * sizeof (int));
          }

          如何保護(hù)數(shù)據(jù)

          通常說來,在多線程程序中保存數(shù)據(jù)需要將保存數(shù)據(jù)的變量與操作系統(tǒng)的線程鎖對象關(guān)聯(lián)起來。在讀取或者設(shè)定變量值的時候,需要首先調(diào)用操作系統(tǒng)API函數(shù)來獲取操作系統(tǒng)的線程鎖對象。在讀取或設(shè)定好變量值后,需要將線程鎖對象釋放掉。在一個特定的時間內(nèi),操作系統(tǒng)只允許一個線程獲得特定的線程鎖對象。一旦線程調(diào)用操作系統(tǒng)API函數(shù)試圖獲取另一個線程正在持有的線程鎖對象,那么試圖獲取線程鎖對象的線程回在操作系統(tǒng)API獲取函數(shù)中等待,直到擁有線程鎖對象的線程將它釋放掉后才返回。試圖獲取其它線程持有的線程鎖對象的線程被稱為阻塞線程。LabWindows/CVI Utility Library提供了三種保護(hù)數(shù)據(jù)的機(jī)制:線程鎖、線程安全變量和線程安全隊(duì)列。

          線程鎖對操作系統(tǒng)提供的簡單的線程鎖對象進(jìn)行了封裝。在三種情況下,你可能要使用到線程鎖。如果有一段需要訪問多個共享數(shù)據(jù)變量的代碼,那么在運(yùn)行代碼前需要獲得線程鎖,而在代碼運(yùn)行后釋放線程鎖。與對每段數(shù)據(jù)都進(jìn)行保護(hù)相比,這個方法的好處是代碼更為簡單,而且不容易出錯。缺點(diǎn)是減低了性能,因?yàn)槌绦蛑械木€程持有線程鎖的時間可能會比實(shí)際需要的時間長,這會造成其它線程為獲得線程鎖而阻塞(等待)的時間變長。使用線程鎖的另一種情況是需要對訪問非線程安全的第三方庫函數(shù)時進(jìn)行保護(hù)。例如,有一個非線程安全的DLL用于控制硬件設(shè)備而你需要在多個線程中調(diào)用這個DLL,那么可以在線程中調(diào)用DLL前創(chuàng)建需要獲得的線程鎖。第三種情況是,你需要使用線程鎖來保護(hù)多個程序間共享的資源。共享內(nèi)存就是這樣一種資源。

          線程安全變量技術(shù)將操作系統(tǒng)的線程鎖對象和需要保護(hù)的數(shù)據(jù)結(jié)合起來。與使用線程鎖來保護(hù)一段數(shù)據(jù)相比,這種方法更為簡單而且不容易出錯。你必須使用線程安全變量來保護(hù)所有類型的數(shù)據(jù),包括結(jié)構(gòu)體類型。線程安全變量比線程鎖更不容易出錯,是因?yàn)橛脩粜枰{(diào)用Utility Library API函數(shù)來訪問數(shù)據(jù)。而API函數(shù)獲取操作系統(tǒng)的線程鎖對象,避免用戶不小心在未獲取OS線程鎖對象的情況下對數(shù)據(jù)進(jìn)行訪問的錯誤。線程安全變量技術(shù)比線程鎖更簡單,因?yàn)橛脩糁恍枰褂靡粋€變量(線程安全變量句柄),而線程鎖技術(shù)則需要使用兩個變量(線程鎖句柄和需要保護(hù)的數(shù)據(jù)本身)。

          線程安全隊(duì)列是一種在線程間進(jìn)行安全的數(shù)組數(shù)據(jù)傳遞的機(jī)制。在程序中有一個線程生成數(shù)組數(shù)據(jù)而另外一個線程對數(shù)組數(shù)據(jù)進(jìn)行處理時,需要使用線程安全隊(duì)列。這類程序的一個例子就是在一個線程中采集數(shù)據(jù),而在另一個線程中分析數(shù)據(jù)或者將數(shù)據(jù)顯示在LabWindows/CVI的用戶界面上。與一個數(shù)組類型的線程安全變量相比,線程安全隊(duì)列有著如下的優(yōu)勢:

          • 線程安全隊(duì)列在其內(nèi)部使用了一種鎖策略,一個線程可以從隊(duì)列讀取數(shù)據(jù)而同時另一個線程向隊(duì)列中寫入數(shù)據(jù)(例如,讀取和寫入線程不會互相阻塞)。
          • 用戶可以為基于事件的訪問配置線程安全隊(duì)列。用戶可以注冊一個讀取回調(diào)函數(shù),在隊(duì)列中有一定數(shù)量的數(shù)據(jù)可用時,調(diào)用這個函數(shù),并且/或者注冊一個寫入回調(diào)函數(shù),在隊(duì)列中有一定的空間可用時,調(diào)用這個函數(shù)。
          • 用戶可以對線程安全隊(duì)列進(jìn)行配置,使得在數(shù)據(jù)增加而空間已滿時,隊(duì)列可以自動生長。

          線程鎖技術(shù)

          在程序初始化的時候,調(diào)用CmtNewLock函數(shù)來為每個需要保護(hù)的數(shù)據(jù)集合創(chuàng)建線程鎖。這個函數(shù)返回一個句柄,用戶可以使用它在后續(xù)的函數(shù)調(diào)用中指定線程鎖。在訪問由鎖保護(hù)的數(shù)據(jù)和代碼前,線程必須調(diào)用CmtGetLock函數(shù)來獲取線程鎖。在訪問數(shù)據(jù)后,線程必須調(diào)用CmtReleaseLock函數(shù)來釋放線程鎖。在同一個線程中,可以多次調(diào)用CmtGetLock(不會對后續(xù)調(diào)用產(chǎn)生阻塞),但是用戶每一次調(diào)用CmtGetLock都需要調(diào)用一次CmtReleaseLock來釋放。在程序退出時,調(diào)用CmtDiscardLock函數(shù)來釋放線程鎖資源。下面的代碼演示了如何使用LabWindows/CVI Utility Library中的線程鎖來保護(hù)全局變量。

          int lock;
          int count;

          int main (int argc, char *argv[])
          {
          int functionId;
          CmtNewLock (NULL, 0, &lock);
          CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE, ThreadFunction, NULL, &functionId);
          CmtGetLock (lock);
          count++;
          CmtReleaseLock (lock);
          CmtWaitForThreadPoolFunctionCompletion (DEFAULT_THREAD_POOL_HANDLE, functionId, 0);
          CmtDiscardLock (lock);
          }
          int CVICALLBACK ThreadFunction (void *functionData)
          {
          CmtGetLock(lock);
          count++;
          CmtReleaseLock(lock);
          return 0;
          }

          線程安全變量

          線程安全變量技術(shù)將數(shù)據(jù)和操作系統(tǒng)線程鎖對象結(jié)合成為一個整體。這個方法避免了多線程編程中一個常見的錯誤:程序員在訪問變量時往往忘記首先去獲得鎖。這種方法還使得在函數(shù)間傳遞保護(hù)的數(shù)據(jù)變得容易,因?yàn)橹恍枰獋鬟f線程安全變量句柄而不需要既傳遞線程鎖句柄又要傳遞保護(hù)的變量。LabWindows/CVI Utility Library API中包含了幾種用于創(chuàng)建和訪問線程安全變量的函數(shù)。利用這些函數(shù)可以創(chuàng)建任何類型的線程安全變量。因?yàn)?,傳遞到函數(shù)中的參數(shù)在類型上是通用的,而且不提供類型安全。通常,你不會直接調(diào)用LabWindows/CVI Utility Library中的線程安全變量函數(shù)。

          LabWindows/CVI Utility Library中的頭文件中包含了一些宏,它們提供了配合Utility Library函數(shù)使用的類型安全的封裝函數(shù)。除了提供類型安全,這些宏還幫助避免了多線程編程中的其它兩個常見錯誤。這些錯誤是在訪問數(shù)據(jù)后忘記釋放鎖對象,或者是在前面沒有獲取鎖對象時試圖釋放鎖對象。使用DefineThreadSafeScalarVar和DefineThreadSafeArrayVar宏來創(chuàng)建線程安全變量和類型安全的函數(shù)供使用和訪問。如果需要從多個源文件中訪問線程安全變量,請?jiān)趇nclude(.h)文件中使用DeclareThreadSafeScalarVar或者DeclareThreadSafeArrayVar宏來創(chuàng)建訪問函數(shù)的聲明。DefineThreadSafeScalarVar(datatype,VarName,maxGetPointerNestingLevel)宏創(chuàng)建以下訪問函數(shù):

          int InitializeVarName (void);
          void UninitializeVarName (void);
          datatype *GetPointerToVarName (void);
          void ReleasePointerToVarName (void);
          void SetVarName (datatype val);
          datatype GetVarName (void);

          注意事項(xiàng):這些宏使用傳遞進(jìn)來的第二個參數(shù)(在這個例子中為VarName)作為標(biāo)識來為線程安全變量創(chuàng)建自定義的訪問函數(shù)名稱。

          注意事項(xiàng):maxGetPointerNestingLevel參數(shù)將在“檢測GetPointerToVarName不匹配調(diào)用”一節(jié)中進(jìn)行進(jìn)一步討論。

          在第一次訪問線程安全變量前首先調(diào)用一次(只在一個線程里)InitializeVarName函數(shù)。在程序中止前調(diào)用UninitializeVarName函數(shù)。如果需要對變量當(dāng)前的值進(jìn)行更改(如,增加一個整數(shù)的值),那么請調(diào)用GetPointerToVarName函數(shù),更改變量值,然后調(diào)用ReleasePointerToVarName函數(shù)。在同一個線程中,可以多次調(diào)用GetPointerToVarName函數(shù)(在后續(xù)的調(diào)用中不會發(fā)生阻塞),但是必須調(diào)用相同次數(shù)的ReleasePointerToVarName函數(shù)與GetPointerToVarName一一對應(yīng)。如果在相同的線程中,調(diào)用了ReleasePointerToVarName函數(shù),而前面沒有與之相匹配的GetPointerToVarName調(diào)用,那么ReleasePointerToVarName將會報告一個run-time error錯誤。

          如果需要對變量值進(jìn)行設(shè)定而不需要考慮其當(dāng)前值,那么請調(diào)用SetVarName函數(shù)。如果需要獲得變量的當(dāng)前值,請調(diào)用GetVarName函數(shù)。需要了解的一點(diǎn)是,在GetVarName從內(nèi)存中讀出變量值后而在其將變量值返回給你前,變量的值是有可能改變的。

          下面的代碼顯示了如何使用線程安全變量作為前面例子中提到的計(jì)數(shù)變量。

          DefineThreadSafeScalarVar (int, Count, 0);
          int CVICALLBACK ThreadFunction (void *functionData);

          int main (int argc, char *argv[])
          {
          int functionId;
          int *countPtr;

          InitializeCount();
          CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE, ThreadFunction, NULL, &functionId);
          countPtr = GetPointerToCount();
          (*countPtr)++;
          ReleasePointerToCount();
          CmtWaitForThreadPoolFunctionCompletion (DEFAULT_THREAD_POOL_HANDLE, functionId, 0);
          UninitializeCount();
          return 0;
          }
          int CVICALLBACK ThreadFunction (void *functionData)
          {
          int *countPtr;

          countPtr = GetPointerToCount();
          (*countPtr)++;
          ReleasePointerToCount();
          return 0;
          }

          使用數(shù)組作為線程安全變量


          DefineThreadSafeArrayVar宏與DefineThreadSafeScalarVar宏相似,但是它還需要一個額外的參數(shù)來指定數(shù)組中元素的個數(shù)。同時,與DefineThreadSafeScalarVar不同,DefineThreadSafeArrayVar沒有定義GetVarName和SetVarName函數(shù)。下面的聲明定義了有10個整數(shù)的線程安全數(shù)組。
          DefineThreadSafeArrayVar (int, Array, 10, 0);

          將多個變量結(jié)合成單個線程安全變量

          如果有多個彼此相關(guān)的變量,那么必須禁止兩個線程同時對這些變量進(jìn)行修改。例如,有一個數(shù)組和記錄數(shù)組中有效數(shù)據(jù)數(shù)目的count變量。如果一個線程需要刪除數(shù)組中的數(shù)據(jù),那么在另一個線程訪問數(shù)據(jù)前,必須對數(shù)組和變量count值進(jìn)行更新。雖然可以使用單個LabWindows/CVI Utility Library線程鎖來對這兩種數(shù)據(jù)的訪問保護(hù),但是更安全的做法是定義一個結(jié)構(gòu)體,然后使用這個結(jié)構(gòu)體作為線程安全變量。下面的例子顯示了如何使用線程安全變量來安全地向數(shù)組中填加一個數(shù)據(jù)。

          typedef struct {
          int data[500];
          int count;
          } BufType;

          DefineThreadSafeVar(BufType, SafeBuf);

          void StoreValue(int val)
          {
          BufType *safeBufPtr;
          safeBufPtr = GetPointerToSafeBuf();
          safeBufPtr->data[safeBufPtr->count] = val;
          safeBufPtr->count++;
          ReleasePointerToSafeBuf();
          }

          檢測對GetPointerToVarName的不匹配調(diào)用

          可以通過DefineThreadSafeScalarVar和DefineThreadSafeArrayVar的最后一個參數(shù)(maxGetPointerNestingLevel),來指定最大數(shù)目的嵌套調(diào)用。通??梢园堰@個參數(shù)設(shè)為0,這樣GetPointerToVarName在檢測到同一線程中對GetPointerToVarName的兩次連續(xù)調(diào)用而中間沒有對ReleasePointerToVarName進(jìn)行調(diào)用時,就會報出一個運(yùn)行錯誤。例如,下面的代碼在第二次執(zhí)行的時候會報出run-time error的錯誤,因?yàn)樗浟苏{(diào)用ReleasePointerToCount函數(shù)。

          int IncrementCount (void)
          {
          int *countPtr;

          countPtr = GetPointerToCount(); /* run-time error on second execution */
          (*countPtr)++;
          /* Missing call to ReleasePointerToCount here */
          return 0;
          }

          如果代碼中必須對GetPointerToVarName進(jìn)行嵌套調(diào)用時,那么可將maxGetPointerNestingLevel參數(shù)設(shè)為一個大于零的整數(shù)。例如,下面的代碼將maxGetPointerNestingLevel參數(shù)設(shè)定為1,因此它允許對GetPointerToVarName進(jìn)行一級嵌套調(diào)用。

          DefineThreadSafeScalarVar (int, Count, 1);
          int Count (void)
          {
          int *countPtr;
          countPtr = GetPointerToCount();
          (*countPtr)++;
          DoSomethingElse(); /* calls GetPointerToCount */
          ReleasePointerToCount ();
          return 0;
          }
          void DoSomethingElse(void)
          {
          int *countPtr;
          countPtr = GetPointerToCount(); /* nested call to GetPointerToCount */
          ... /* do something with countPtr */
          ReleasePointerToCount ();
          }

          如果不知道GetPointerToVarName的最大嵌套級別,那么請傳遞TSV_ALLOW_UNLIMITED_NESTING來禁用對GetPointerToVarName函數(shù)的不匹配調(diào)用檢查。

          線程安全隊(duì)列

          使用LabWindows/CVI Utility Library的線程安全隊(duì)列,可以在線程間安全地傳遞數(shù)據(jù)。當(dāng)需要用一個線程來采集數(shù)據(jù)而用另一個線程來處理數(shù)據(jù)時,這種技術(shù)非常有用。線程安全隊(duì)列在其內(nèi)部處理所有的數(shù)據(jù)鎖定。通常說來,應(yīng)用程序中的輔助線程獲取數(shù)據(jù),而主線程在數(shù)據(jù)可用時讀取數(shù)據(jù)然后分析并/或顯示數(shù)據(jù)。下面的代碼顯示了線程如何使用線程安全隊(duì)列將數(shù)據(jù)傳遞到另外一個線程。在數(shù)據(jù)可用時,主線程利用回調(diào)函數(shù)來讀取數(shù)據(jù)。

          int queue;
          int panelHandle;

          int main (int argc, char *argv[])
          {
          if (InitCVIRTE (0, argv, 0) == 0)
          return -1; /* out of memory */
          if ((panelHandle = LoadPanel(0, "DAQDisplay.uir", PANEL)) < 0)
          return -1;
          /* create queue that holds 1000 doubles and grows if needed */
          CmtNewTSQ(1000, sizeof(double), OPT_TSQ_DYNAMIC_SIZE, &queue);
          CmtInstallTSQCallback (queue, EVENT_TSQ_ITEMS_IN_QUEUE, 500, QueueReadCallback, 0, CmtGetCurrentThreadID(), NULL);
          CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE, DataAcqThreadFunction, NULL, NULL);
          DisplayPanel (panelHandle);
          RunUserInterface();
          . . .
          return 0;
          }
          void CVICALLBACK QueueReadCallback (int queueHandle, unsigned int event, int value, void *callbackData)
          {
          double data[500];
          CmtReadTSQData (queue, data, 500, TSQ_INFINITE_TIMEOUT, 0);
          }

          6. 避免死鎖

          當(dāng)兩個線程同時等待對方持有的線程鎖定對象時,代碼就不能繼續(xù)運(yùn)行了。這種狀況被稱為死鎖。如果用戶界面線程發(fā)生死鎖,那么它就不能響應(yīng)用戶的輸入。用戶必須非正常地結(jié)束程序。下面的例子解釋了死鎖是如何發(fā)生的。

          線程1:調(diào)用函數(shù)來獲取線程鎖A(線程1:無線程鎖,線程2:無線程鎖)。

          線程1:從獲取線程鎖的函數(shù)返回(線程1:持有線程鎖A,線程2:無線程鎖)。

          切換到線程2:(線程1:持有線程鎖A,線程2:無線程鎖)。

          線程2:調(diào)用函數(shù)來獲取線程鎖B(線程1:持有線程鎖A,線程2:無線程鎖)。

          線程2:從獲取線程鎖的函數(shù)返回(線程1:持有線程鎖A,線程2:持有線程鎖B)。

          線程2:調(diào)用函數(shù)來獲取線程鎖A(線程1:持有線程鎖A,線程2:持有線程鎖B)。

          線程2:由于線程1持有線程鎖A而被阻塞(線程1:持有線程鎖A,線程2:持有線程鎖B)。

          切換到線程1:調(diào)用函數(shù)來獲取線程鎖B(線程1:持有線程鎖A,線程2:持有線程鎖B)。

          線程1:調(diào)用函數(shù)來獲取線程鎖B(線程1:持有線程鎖A,線程2:持有線程鎖B)。

          線程1:由于線程2持有線程鎖A而被阻塞(線程1:持有線程鎖A,線程2:持有線程鎖B)。

          與不對數(shù)據(jù)進(jìn)行保護(hù)時產(chǎn)生的錯誤相似,由于程序運(yùn)行的情況不同導(dǎo)致線程切換的時序不同,死鎖錯誤間歇性地發(fā)生。例如,如果直到線程1持有線程鎖A和B后才切換到線程2,那么線程1就可以完成工作而釋放掉這些線程鎖,讓線程2在晚些時候獲取到。就像上面所說的那樣,死鎖現(xiàn)象只有在線程同時獲取線程鎖時才會發(fā)生。所以你可以使用簡單的規(guī)則來避免這種死鎖。當(dāng)需要獲取多個線程鎖對象時,程序中的每個線程都需要按照相同的順序來獲取線程鎖對象。下面的LabWindows/CVI Utility Library函數(shù)獲取線程鎖對象,并且返回時并不釋放這些對象。

          • CmtGetLock
          • CmtGetTSQReadPtr
          • CmtGetTSQWritePtr

          注意事項(xiàng):通常說來,不需要直接調(diào)用CmtGetTSVPtr函數(shù)。它是通過DeclareThreadSafeVariable宏創(chuàng)建的GetPtrToVarName函數(shù)調(diào)用的。因此,對于調(diào)用的GetPtrToVarName函數(shù)需要將它作為線程鎖對象獲取函數(shù)來對待,應(yīng)該注意死鎖保護(hù)的問題。
          The following Windows SDK functions can acquire thread-locking objects without releasing them before returning.Note:This is not a comprehensive list.

          下面的Windows SDK函數(shù)可以獲取線程鎖對象但在返回時并不釋放這些對象。注意,這不是完整的列表。

          • EnterCriticalSection
          • CreateMutex
          • CreateSemaphore
          • SignalObjectAndWait
          • WaitForSingleObject
          • MsgWaitForMultipleObjectsEx

          7. 監(jiān)視和控制輔助線程

          在把一個函數(shù)調(diào)度到獨(dú)立的線程中運(yùn)行時,需要對被調(diào)度函數(shù)的運(yùn)行狀態(tài)進(jìn)行監(jiān)視。為了獲得被調(diào)度函數(shù)的運(yùn)行狀態(tài),調(diào)用CmtGetThreadPoolFunctionAttribute來獲得ATTR_TP_FUNCTION_EXECUTION_STATUS屬性的值。也可以注冊一個回調(diào)函數(shù),線程池調(diào)用之后立即運(yùn)行被調(diào)度的函數(shù)和/或開始運(yùn)行后立即由線程池調(diào)用。如果需要注冊這樣的回調(diào)函數(shù),必須使用CmtScheduleThreadFunctionAdv來對函數(shù)進(jìn)行調(diào)度。

          通常說來,輔助進(jìn)程需要在主線程結(jié)束程序前完成。如果主線程在輔助線程完成之前結(jié)束,那么輔助線程將不能夠?qū)⒎峙涞降馁Y源清理掉。同時,可能導(dǎo)致這些輔助線程所使用的庫函數(shù)也不能被正確清除。

          可以調(diào)用CmtWaitForThreadPoolFunctionCompletion函數(shù)來安全地等待輔助線程結(jié)束運(yùn)行,然后允許主線程結(jié)束。

          在一些例子中,輔助線程函數(shù)必須持續(xù)完成一些工作直到主線程讓它停止下來。在這類情況下,輔助線程通常在while循環(huán)中完成任務(wù)。while循環(huán)的條件是主線程中設(shè)定的整數(shù)變量,當(dāng)主線程需要告知輔助線程停止運(yùn)行時,將其設(shè)為非零整數(shù)。下面的代碼顯示了如何使用while循環(huán)來控制輔助線程何時結(jié)束執(zhí)行。

          volatile int quit = 0;

          int main (int argc, char *argv[])
          {
          int functionId;
          CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE, ThreadFunction, NULL, &functionId);
          // This would typically be done inside a user interface
          // Quit button callback.
          quit = 1;
          CmtWaitForThreadPoolFunctionCompletion (DEFAULT_THREAD_POOL_HANDLE, functionId, 0);
          return 0;
          }
          int CVICALLBACK ThreadFunction (void *functionData)
          {
          while (!quit) {
          . . .
          }
          return 0;
          }

          注意事項(xiàng):如果使用volatile關(guān)鍵字,這段代碼在經(jīng)過優(yōu)化的編譯器(如Microsoft Visual C++)后功能是正常的。優(yōu)化的編譯器確定while循環(huán)中的代碼不會修改quit變量的值。因此,作為優(yōu)化,編譯器可能只使用quit變量在while循環(huán)條件中的初始值。使用volatile關(guān)鍵字是告知編譯器另一個線程可能會改變quit變量的值。這樣,編譯器在每次循環(huán)運(yùn)行時都使用更新過后的quit變量值。

          有些時候,當(dāng)主線程進(jìn)行其他任務(wù)的時候需要暫停輔助線程的運(yùn)行。如果你暫停正在運(yùn)行操作系統(tǒng)代碼的線程,可能會使得操作系統(tǒng)處于非法狀態(tài)。因此,在需要暫停的線程中需要始終調(diào)用Windows SDK的SuspendThreadfunction函數(shù)。這樣,可以確保線程在運(yùn)行關(guān)鍵代碼時不被暫停。在另一個線程中調(diào)用Windows SDK的ResumeThreadfunction是安全的。下面的代碼展示了如何使用它們。

          volatile int quit = 0;

          int main (int argc, char *argv[])
          {
          int functionId;
          CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE, ThreadFunction, NULL, &functionId);
          // This would typically be done inside a user interface
          // Quit button callback.
          quit = 1;
          CmtWaitForThreadPoolFunctionCompletion (DEFAULT_THREAD_POOL_HANDLE, functionId, 0);
          return 0;
          }
          int CVICALLBACK ThreadFunction (void *functionData)
          {
          while (!quit) {
          . . .
          }
          return 0;
          }

          8. 進(jìn)程和線程優(yōu)先級

          在Windows操作系統(tǒng)中,可以指定每個進(jìn)程和線程工作的相對重要性(被稱為優(yōu)先級)。如果給予進(jìn)程或線程以較高的優(yōu)先級,那么它們將獲得比優(yōu)先級較低的線程更好的優(yōu)先選擇。這意味著當(dāng)多個線程需要運(yùn)行的時候,具有最高優(yōu)先級的線程首先運(yùn)行。

          Windows將優(yōu)先級分類。同一進(jìn)程中的所有線程擁有相同的優(yōu)先級類別。同一進(jìn)程中的每個線程都有著與進(jìn)程優(yōu)先級類別相關(guān)的優(yōu)先級??梢哉{(diào)用Windows SDK中的SetProcessPriorityClass函數(shù)來設(shè)定系統(tǒng)中線程的優(yōu)先級。

          NI公司不推薦用戶將線程的優(yōu)先級設(shè)為實(shí)時優(yōu)先級,除非只在很短時間內(nèi)這樣做。當(dāng)進(jìn)程被設(shè)為實(shí)時優(yōu)先級時,它運(yùn)行時系統(tǒng)中斷會被阻塞。這會造成鼠標(biāo)、鍵盤、硬盤及其它至關(guān)重要的系統(tǒng)特性不能工作,并很可能造成系統(tǒng)被鎖定。

          如果你是使用CmtScheduleThreadFunctionAdv函數(shù)來將函數(shù)調(diào)度到線程池中運(yùn)行,那么還可以指定執(zhí)行所調(diào)度函數(shù)的線程的優(yōu)先級。線程池在運(yùn)行被調(diào)度的函數(shù)前會改變線程優(yōu)先級。在函數(shù)結(jié)束運(yùn)行后,線程池會將線程優(yōu)先級恢復(fù)到原來的優(yōu)先級??墒褂肅mtScheduleThreadFunctionAdv函數(shù)來在默認(rèn)的和自定義的線程池中指定線程的優(yōu)先級。

          在創(chuàng)建自定義的LabWindows/CVI Utility Library線程池(調(diào)用CmtNewThreadPool函數(shù))時,可以設(shè)定池中各線程的默認(rèn)優(yōu)先級。

          9. 消息處理

          每個創(chuàng)建了窗口的線程必須對Windows消息進(jìn)行處理以避免系統(tǒng)鎖定。用戶界面庫中的RunUserInterfacefunction函數(shù)包含了處理LabWindows/CVI用戶界面事件和Windows消息的循環(huán)。用戶界面庫中的GetUserEvent和ProcessSystemEventsfunctions函數(shù)在每次被調(diào)用時對Windows消息進(jìn)行處理。如果下列情況中的之一被滿足,那么程序中的每個線程都需要調(diào)用GetUserEventor和ProcessSystemEventsregularly函數(shù)來處理Windows消息。

          • 線程創(chuàng)建了窗口但沒有調(diào)用RunUserInterface函數(shù)。
          • 線程創(chuàng)建了窗口并調(diào)用了RunUserInterface函數(shù),但是在返回到RunUserInterface循環(huán)前需要運(yùn)行的回調(diào)函數(shù)占用了大量時間(多于幾百毫秒)。

          但是,在代碼中的某些地方不適合用于處理Windows消息。在LabWindows/CVI的用戶界面線程中調(diào)用了GetUserEvent、ProcessSystemEvents或RunUserInterface函數(shù)時,線程可以調(diào)用一個用戶界面回調(diào)函數(shù)。如果在用戶界面回調(diào)函數(shù)中調(diào)用這些函數(shù)之一,那么線程將調(diào)用另外一個回調(diào)函數(shù)。除非需要這樣做,否則這種事件將產(chǎn)生不可預(yù)知的行為。

          Utility Library中的多線程函數(shù)會造成線程在循環(huán)中等待,允許你指定是否在等待線程中對消息進(jìn)行處理。例如,CmtWaitForThreadPoolFunctionCompletion函數(shù)中有個Option參數(shù),可以使用它來指定處理Windows消息的等待線程。

          有的時候,線程對窗口的創(chuàng)建不是那么顯而易見的。用戶界面庫函數(shù)如LoadPanel、CreatePanel和FileSelectPopup等都創(chuàng)建了用于顯示和丟棄的窗口。這些函數(shù)還為每個調(diào)用它們的線程創(chuàng)建了隱藏的窗口。在銷毀可見的窗口時,這個隱藏的窗口并沒有被銷毀。除了這些用戶界面庫函數(shù)外,各種其它的LabWindows/CVI庫函數(shù)和Windows API函數(shù)創(chuàng)建了隱藏的背景窗口。為了避免系統(tǒng)的鎖定,必須在線程中對使用這兩種方法創(chuàng)建的窗口的Windows消息進(jìn)行處理。

          10. 使用線程局部變量

          線程局部變量與全局變量相似,可以在任意線程中對它們進(jìn)行訪問。但是,全局變量對于所有線程只保存一個值,而線程局部變量為每個訪問的線程保存一個獨(dú)立的值。當(dāng)程序中需要同時在多個上下文中進(jìn)行相同的任務(wù),而其中每個上下文都對應(yīng)一個獨(dú)立的線程時,通常需要使用到線程局部變量。例如,你編寫了一個并行的測試程序,其中的每個線程處理一個待測單元,那么你可能需要使用線程局部變量來保存每個單元的特定信息(例如序列號)。

          Windows API提供了用于創(chuàng)建和訪問線程局部變量的機(jī)制,但是該機(jī)制對每個進(jìn)程中可用的線程局部變量的數(shù)目進(jìn)行了限定。LabWindows/CVI Utility Library中的線程局部變量函數(shù)沒有這種限制。下面的代碼展示了如何創(chuàng)建和訪問一個保存了整數(shù)的線程局部變量。

          volatile int quit = 0;
          volatile int suspend = 0;
          int main (int argc, char *argv[])
          {
          int functionId;
          HANDLE threadHandle;
          CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE, ThreadFunction, NULL, &functionId);
          . . .
          // This would typically be done in response to user input or a
          // change in program state.
          suspend = 1;
          . . .
          CmtGetThreadPoolFunctionAttribute (DEFAULT_THREAD_POOL_HANDLE, functionId, ATTR_TP_FUNCTION_THREAD_HANDLE, &threadHandle);
          ResumeThread (threadHandle);
          . . .
          return 0;
          }
          int CVICALLBACK ThreadFunction (void *functionData)
          {
          while (!quit) {
          if (suspend) {
          SuspendThread (GetCurrentThread ());
          suspend = 0;
          }
          . . .
          }
          return 0;
          }

          int CVICALLBACK ThreadFunction (void *functionData);
          int tlvHandle;
          int gSecondaryThreadTlvVal;

          int main (int argc, char *argv[])
          {
          int functionId;
          int *tlvPtr;

          if (InitCVIRTE (0, argv, 0) == 0)
          return -1; /* out of memory */
          CmtNewThreadLocalVar (sizeof(int), NULL, NULL, NULL, &tlvHandle);
          CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE, ThreadFunction, 0, &functionId);
          CmtWaitForThreadPoolFunctionCompletion (DEFAULT_THREAD_POOL_HANDLE, functionId, 0);
          CmtGetThreadLocalVar (tlvHandle, &tlvPtr);
          (*tlvPtr)++;
          // Assert that tlvPtr has been incremented only once in this thread.
          assert (*tlvPtr == gSecondaryThreadTlvVal);
          CmtDiscardThreadLocalVar (tlvHandle);
          return 0;
          }
          int CVICALLBACK ThreadFunction (void *functionData)
          {
          int *tlvPtr;

          CmtGetThreadLocalVar (tlvHandle, &tlvPtr);
          (*tlvPtr)++;
          gSecondaryThreadTlvVal = *tlvPtr;
          return 0;
          }

          11. 在線程局部變量中存儲動態(tài)分配的數(shù)據(jù)

          如果你使用線程局部變量來存儲動態(tài)分配到的資源,那么你需要釋放掉分配的資源的每一個拷貝。也就是說,你需要釋放掉每個線程中分配到的資源拷貝。使用LabWindows/CVI的線程局部變量,你可以指定用于銷毀線程局部變量的回調(diào)函數(shù)。當(dāng)你銷毀線程局部變量時,每個訪問過變量的線程都會調(diào)用指定的回調(diào)函數(shù)。下面的代碼展示了如何創(chuàng)建和訪問保存了動態(tài)分配的字符串的線程局部變量。

          int CVICALLBACK ThreadFunction (void *functionData);
          void CVICALLBACK StringCreate (char *strToCreate);
          void CVICALLBACK StringDiscard (void *threadLocalPtr, int event, void *callbackData, unsigned int threadID);
          int tlvHandle;
          volatile int quit = 0;
          volatile int secondStrCreated = 0;

          int main (int argc, char *argv[])
          {
          int functionId;

          if (InitCVIRTE (0, argv, 0) == 0)
          return -1; /* out of memory */
          CmtNewThreadLocalVar (sizeof(char *), NULL, StringDiscard, NULL, &tlvHandle);
          CmtScheduleThreadPoolFunction (DEFAULT_THREAD_POOL_HANDLE, ThreadFunction, "Secondary Thread", &functionId);
          StringCreate ("Main Thread");
          while (!secondStrCreated){
          ProcessSystemEvents ();
          Delay (0.001);
          }
          CmtDiscardThreadLocalVar (tlvHandle);
          quit = 1;
          CmtWaitForThreadPoolFunctionCompletion (DEFAULT_THREAD_POOL_HANDLE, functionId, 0);
          return 0;
          }
          int CVICALLBACK ThreadFunction (void *functionData)
          {
          char **sString;

          // Create thread local string variable
          StringCreate ((char *)functionData);

          // Get thread local string and print it
          CmtGetThreadLocalVar (tlvHandle, &sString);
          printf ("Thread local string: %sn", *sString);

          secondStrCreated = 1;

          while (!quit)
          {
          ProcessSystemEvents ();
          Delay (0.001);
          }

          return 0;
          }
          void CVICALLBACK StringCreate (char *strToCreate)
          {
          char **tlvStringPtr;
          CmtGetThreadLocalVar (tlvHandle, &tlvStringPtr);
          *tlvStringPtr = malloc (strlen (strToCreate) + 1);
          strcpy (*tlvStringPtr, strToCreate);
          }
          void CVICALLBACK StringDiscard (void *threadLocalPtr, int event, void *callbackData, unsigned int threadID)
          {
          char *str = *(char **)threadLocalPtr;
          free (str);
          }

          一些分配的資源必須在分配到它們的線程中釋放。這些資源被稱為擁有線程關(guān)聯(lián)度。例如,面板必須在創(chuàng)建它的線程中銷毀掉。在調(diào)用CmtDiscardThreadLocalVar時,Utility Library在線程中調(diào)用被稱為CmtDiscardThreadLocalVar的線程局部變量銷毀回調(diào)函數(shù)。Utility Library為每一個訪問過該變量的線程調(diào)用一次銷毀回調(diào)函數(shù)。它將threadID參數(shù)傳遞給銷毀回調(diào)函數(shù),這個參數(shù)指定了調(diào)用銷毀回調(diào)函數(shù)的線程的ID號。你可以使用這個線程ID來確定是否可以直接釋放掉擁有線程關(guān)聯(lián)度的資源還是必須在正確的線程中調(diào)用Toolslib中的PostDeferredCallToThreadAndWait函數(shù)來釋放資源。下面的代碼顯示了如何更改前面的例子以在分配字符串的線程中將它們釋放掉。

          void CVICALLBACK StringDiscard (void *threadLocalPtr, int event, void *callbackData, unsigned int threadID)
          {
          char *str = *(char **)threadLocalPtr;

          if (threadID == CmtGetCurrentThreadID ())
          free (str);
          else
          PostDeferredCallToThreadAndWait (free, str, threadID, POST_CALL_WAIT_TIMEOUT_INFINITE);
          }

          12. 在獨(dú)立線程中運(yùn)行的回調(diào)函數(shù)

          使用LabWindows/CVI中的一些庫,你可以在系統(tǒng)創(chuàng)建的線程中接收回調(diào)函數(shù)。因?yàn)檫@些庫會自動創(chuàng)建執(zhí)行回調(diào)函數(shù)的線程,所以你不需要創(chuàng)建線程或者將函數(shù)調(diào)度到單獨(dú)的線程中執(zhí)行。在程序中,你仍然需要對這些線程和其它線程間共享的數(shù)據(jù)進(jìn)行保護(hù)。這些回調(diào)函數(shù)的實(shí)現(xiàn)通常被稱為是異步事件。

          LabWindows/CVI的GPIB/GPIB 488.2庫中,可以調(diào)用ibnotify來注冊事件發(fā)生時GPIB/GPIB 488.2庫調(diào)用的回調(diào)函數(shù)。你可以為每一個電路板或器件指定一個回調(diào)函數(shù)??梢詾槭录付ㄕ{(diào)用的回調(diào)函數(shù)。GPIB/GPIB 488.2庫會創(chuàng)建用于執(zhí)行回調(diào)函數(shù)的線程。

          在LabWindows/CVI的虛擬儀器軟件構(gòu)架 (VISA) 庫中,你可以調(diào)用viInstallHandler函數(shù)來注冊多個事件句柄(回調(diào)函數(shù))用于在特定的ViSession中接收VISA事件(I/O完成、服務(wù)請求等等)類型。VISA庫通常創(chuàng)建獨(dú)立的線程來執(zhí)行回調(diào)函數(shù)。VISA可能會對一個進(jìn)程中的所有回調(diào)函數(shù)使用同一個線程,或者對每個ViSession使用單獨(dú)的線程。你需要為某個指定的事件類型調(diào)用viEnableEvent函數(shù)以通知VISA庫調(diào)用已注冊的事件句柄。

          在LabWindows/CVI VXI庫中,每個中斷或回調(diào)函數(shù)類型都有自己的回調(diào)注冊和使能函數(shù)。例如,為了接收NI-VXI中斷,你必須調(diào)用SetVXIintHandler和EnableVXIint函數(shù)。VXI庫使用自己創(chuàng)建的獨(dú)立線程來執(zhí)行回調(diào)函數(shù)。對于同一進(jìn)程中所有的回調(diào)函數(shù),VXI都使用相同的線程。

          13. 為線程設(shè)定首選的處理器

          可以使用平臺SDK中的SetThreadIdealProcessor函數(shù)來指定執(zhí)行某一線程的處理器。這個函數(shù)的第一個參數(shù)是線程句柄。第二個參數(shù)是以零為索引起始的處理器。可以調(diào)用LabWindows/CVI Utility Library中的CmtGetThreadPoolFunctionAttribute函數(shù),使用ATTR_TP_FUNCTION_THREAD_HANDLE屬性來獲取線程池線程的句柄??梢哉{(diào)用LabWindows/CVI Utility Library中的CmtGetNumberOfProcessors函數(shù)來通過程序來確定運(yùn)行該程序的計(jì)算機(jī)上處理器的數(shù)量。

          可以使用平臺SDK中的SetProcessAffinityMask函數(shù)來指定允許執(zhí)行你的程序的處理器??梢允褂闷脚_SDK中的SetThreadAffinityMask函數(shù)來指定允許執(zhí)行程序中特定線程的處理器。傳遞到SetThreadAffinityMask中的mask變量必須是傳遞到SetProcessAffinityMask中的mask變量的子集。



          關(guān)鍵詞: LabWindowsCVI多線程技

          評論


          技術(shù)專區(qū)

          關(guān)閉