LabWindows?/CVI中的多線程技術(shù)
在程序中使用多線程技術(shù)的原因主要有四個(gè)。最常見(jiàn)的原因是多個(gè)任務(wù)進(jìn)行分割,這些任務(wù)中的一個(gè)或多個(gè)是對(duì)時(shí)間要求嚴(yán)格的而且易被其他任務(wù)的運(yùn)行所干涉。例如,進(jìn)行數(shù)據(jù)采集并顯示用戶界面的程序就很適合使用多線程技術(shù)實(shí)現(xiàn)。在這種類型的程序中,數(shù)據(jù)采集是時(shí)間要求嚴(yán)格的任務(wù),它很可能被用戶界面的任務(wù)打斷。在LabWindows/CVI程序中使用單線程方法時(shí),程序員可能需要從數(shù)據(jù)采集緩沖區(qū)讀出數(shù)據(jù)并將它們顯示到用戶界面的曲線上,然后處理事件對(duì)用戶界面進(jìn)行更新。當(dāng)用戶在界面上進(jìn)行操作(如在圖表上拖動(dòng)光標(biāo))時(shí),線程將繼續(xù)處理用戶界面事件而不能返回到數(shù)據(jù)采集任務(wù),這將導(dǎo)致數(shù)據(jù)采集緩沖區(qū)的溢出。而在LabWindows/CVI程序中使用多線程技術(shù)時(shí),程序員可以將數(shù)據(jù)采集操作放在一個(gè)線程中,而將用戶界面處理放在另一個(gè)線程中。這樣,在用戶對(duì)界面進(jìn)行操作時(shí),操作系統(tǒng)將進(jìn)行線程切換,為數(shù)據(jù)采集線程提供完成任務(wù)所需的時(shí)間。
在程序中使用多線程技術(shù)的第二個(gè)原因是程序中可能需要同時(shí)進(jìn)行低速的輸入/輸出操作。例如,使用儀器來(lái)測(cè)試電路板的程序?qū)亩嗑€程技術(shù)中獲得顯著的性能提升。在LabWindows/CVI程序中使用單線程技術(shù)時(shí),程序員需要從串口發(fā)送數(shù)據(jù),初始化電路板。,程序需要等待電路板完成操作之后,再去初始化測(cè)試儀器。必須要等待測(cè)試儀器完成初始化之后,再進(jìn)行測(cè)量,。在LabWindows/CVI程序中使用多線程技術(shù)時(shí),你可以使用另一個(gè)線程來(lái)初始化測(cè)試儀器。這樣,在等待電路板初始化的同時(shí)等待儀器初始化。低速的輸入/輸出操作同時(shí)進(jìn)行,減少了等待所需要的時(shí)間總開(kāi)銷。
在程序中使用多線程技術(shù)的第三個(gè)原因是借助多處理器計(jì)算機(jī)來(lái)提高性能。計(jì)算機(jī)上的每個(gè)處理器可以都執(zhí)行一個(gè)線程。這樣,在單處理器計(jì)算機(jī)上,操作系統(tǒng)只是使多個(gè)線程看起來(lái)是同時(shí)執(zhí)行的,而在多處理器計(jì)算機(jī)上,操作系統(tǒng)才是真正意義上同時(shí)執(zhí)行多個(gè)線程的。例如,進(jìn)行數(shù)據(jù)采集、將數(shù)據(jù)寫(xiě)入磁盤、分析數(shù)據(jù)并且在用戶界面上顯示分析數(shù)據(jù),這樣的程序很可能通過(guò)多線程技術(shù)和多處理器計(jì)算機(jī)運(yùn)行得到性能提升。將數(shù)據(jù)寫(xiě)到磁盤上和分析用于顯示的數(shù)據(jù)是可以同時(shí)執(zhí)行的任務(wù)。
在程序中使用多線程技術(shù)的第四個(gè)原因是在多個(gè)環(huán)境中同時(shí)執(zhí)行特定的任務(wù)。例如,程序員可以在應(yīng)用程序中利用多線程技術(shù)在測(cè)試艙進(jìn)行并行化測(cè)試。使用單線程技術(shù),應(yīng)用程序需要?jiǎng)討B(tài)分配空間來(lái)保存每個(gè)艙中的測(cè)試結(jié)果。應(yīng)用程序需要手動(dòng)維護(hù)每個(gè)記錄及其對(duì)應(yīng)的測(cè)試艙的關(guān)系。而使用多線程技術(shù),應(yīng)用程序可以創(chuàng)建獨(dú)立的線程來(lái)處理每個(gè)測(cè)試艙。然后,應(yīng)用程序可以使用線程局部變量為每個(gè)線程創(chuàng)建測(cè)試結(jié)果記錄。測(cè)試艙與結(jié)果記錄間的關(guān)系是自動(dòng)維護(hù)的,使應(yīng)用程序代碼得以簡(jiǎn)化。
2. 選擇合適的操作系統(tǒng)微軟公司的Windows 9x系列操作系統(tǒng)不支持多處理器計(jì)算機(jī)。所以,你必須在多處理器計(jì)算機(jī)上運(yùn)行Windows Vista/XP/2000/NT 4.0系統(tǒng)來(lái)享受多處理器帶來(lái)的好處。而且,即使在單處理器計(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)得并不是十分明顯。
對(duì)于程序開(kāi)發(fā),特別是編寫(xiě)和調(diào)試多線程程序而言,Windows Vista/XP/2000/NT 4.0系列操作系統(tǒng)比Windows 9x系列更為穩(wěn)定,當(dāng)運(yùn)行操作系統(tǒng)代碼的線程被暫?;蚪K止的時(shí)候,操作系統(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ī)來(lái)開(kāi)發(fā)多線程程序。
3. LabWindows/CVI中的多線程技術(shù)簡(jiǎn)介NI LabWindows/CVI軟件自二十世紀(jì)九十年代中期誕生之日起就支持多線程應(yīng)用程序的創(chuàng)建。現(xiàn)在,隨著多核CPU的廣泛普及,用戶可以使用LabWindows/CVI來(lái)充分利用多線程技術(shù)的優(yōu)勢(shì)。
與Windows SDK threading API(Windows 軟件開(kāi)發(fā)工具包線程API)相比,LabWindows/CVI的多線程庫(kù)提供了以下多個(gè)性能優(yōu)化:
- Thread pools幫助用戶將函數(shù)調(diào)度到獨(dú)立的線程中執(zhí)行。Thread pools處理線程緩存來(lái)最小化與創(chuàng)建和銷毀線程相關(guān)的開(kāi)銷。
- Thread-safe queues對(duì)線程間的數(shù)據(jù)傳遞進(jìn)行了抽象。一個(gè)線程可以在另一個(gè)線程向隊(duì)列寫(xiě)入數(shù)據(jù)的同時(shí),從隊(duì)列中讀取數(shù)據(jù)。
- Thread-safe variables高效地將臨界代碼段和任意的數(shù)據(jù)類型結(jié)合在一起。用戶可以調(diào)用簡(jiǎn)單的函數(shù)來(lái)獲取臨界代碼段,設(shè)定變量值,然后釋放臨界代碼段。
- Thread locks提供了一致的API并在必要時(shí)自動(dòng)選擇合適的機(jī)制來(lái)簡(jiǎn)化臨界代碼段和互斥量的使用。例如,如果需要在進(jìn)程間共享互斥鎖,或者線程需要在等待鎖的時(shí)候處理消息,LabWindows/CVI會(huì)自動(dòng)使用互斥量。臨界代碼段使用在其它場(chǎng)合中,因?yàn)樗痈咝А?/li>
- Thread-local variables為每個(gè)線程提供變量實(shí)例。操作系統(tǒng)對(duì)每個(gè)進(jìn)程可用的線程局部變量的數(shù)量進(jìn)行了限制。LabWindows/CVI在實(shí)現(xiàn)過(guò)程中對(duì)線程局部變量進(jìn)行了加強(qiáng),程序中的所有線程局部變量只使用一個(gè)進(jìn)程變量。
可以在Utility Library»Multithreading下的LabWindows/CVI庫(kù)函數(shù)樹(shù)狀圖中找到所有的多線程函數(shù)。
4. 在LabWindows/CVI的輔助線程中運(yùn)行代碼單線程程序中的線程被稱為主線程。在用戶告訴操作系統(tǒng)開(kāi)始執(zhí)行特定的程序時(shí),操作系統(tǒng)將創(chuàng)建主線程。在多線程程序中,除了主線程外,程序還通知操作系統(tǒng)創(chuàng)建其他的線程。這些線程被稱為輔助線程。主線程和輔助線程的主要區(qū)別在于它們開(kāi)始執(zhí)行的位置。操作系統(tǒng)從main或者WinMain函數(shù)開(kāi)始執(zhí)行主線程,而由開(kāi)發(fā)人員來(lái)指定輔助線程開(kāi)始執(zhí)行的位置。
在典型的LabWindows/CVI多線程程序中,開(kāi)發(fā)者使用主線程來(lái)創(chuàng)建、顯示和運(yùn)行用戶界面,而使用輔助線程來(lái)進(jìn)行其它時(shí)間要求嚴(yán)格的操作,如數(shù)據(jù)采集等。LabWindows/CVI提供了兩種在輔助進(jìn)程中運(yùn)行代碼的高級(jí)機(jī)制。這兩種機(jī)制是線程池(thread pools)和異步定時(shí)器。線程池適合于執(zhí)行若干次的或者一個(gè)循環(huán)內(nèi)執(zhí)行的任務(wù)。而異步定時(shí)器適合于定期進(jìn)行的任務(wù)。
使用線程池為了使用LabWindows/CVI的線程池在輔助線程中執(zhí)行代碼,需要調(diào)用Utility Library中的CmtScheduleThreadPoolFunction函數(shù)。將需要在輔助線程中運(yùn)行的函數(shù)名稱傳遞進(jìn)來(lái)。線程池將這個(gè)函數(shù)調(diào)度到某個(gè)線程中執(zhí)行。根據(jù)配置情況和當(dāng)前的狀態(tài),線程池可能會(huì)創(chuàng)建新的線程來(lái)執(zhí)行這個(gè)函數(shù)、也可能會(huì)使用已存在的空閑進(jìn)程執(zhí)行函數(shù)或者會(huì)等待一個(gè)活躍的線程變?yōu)榭臻e然后使用該線程執(zhí)行預(yù)定的函數(shù)。傳遞給CmtScheduleThreadPoolFunction的函數(shù)被稱為線程函數(shù)。線程池中的線程函數(shù)可以選擇任意的名稱,但是必須遵循以下原型:
下面的代碼顯示了如何使用CmtScheduleThreadPoolFunction函數(shù)在輔助進(jìn)程中執(zhí)行一個(gè)數(shù)據(jù)采集的線程。
在前面的代碼中,主線程調(diào)用了CmtScheduleThreadPoolFunction函數(shù),使線程池創(chuàng)建了一個(gè)新的線程來(lái)運(yùn)行DataAcqThreadFunction線程函數(shù)。主線程從CmtScheduleThreadPoolFunction函數(shù)返回,而無(wú)須等待DataAcqThreadFunction函數(shù)完成。在輔助線程中的DataAcqThreadFunction函數(shù)與主線程中的調(diào)用是同時(shí)執(zhí)行的。
CmtScheduleThreadPoolFunction函數(shù)的第一個(gè)參數(shù)表示用于進(jìn)行函數(shù)調(diào)度的線程池。LabWindows/CVI的Utility Library中包含了內(nèi)建的默認(rèn)線程池。傳遞常數(shù)DEFAULT_THREAD_POOL_HANDLE表示用戶希望使用默認(rèn)的線程池。但是用戶不能對(duì)默認(rèn)線程池的行為進(jìn)行自定義。用戶可以調(diào)用CmtNewThreadPool函數(shù)來(lái)創(chuàng)建自定義的線程池。CmtNewThreadPool函數(shù)返回一個(gè)線程池句柄,這個(gè)句柄將作為第一個(gè)參數(shù)傳遞給CmtScheduleThreadPoolFunction函數(shù)。程序員需要調(diào)用CmtDiscardThreadPool函數(shù)來(lái)釋放由CmtNewThreadPool函數(shù)創(chuàng)建的線程池資源。
CmtScheduleThreadPoolFunction函數(shù)中的最后一個(gè)參數(shù)返回一個(gè)標(biāo)識(shí)符,用于在后面的函數(shù)調(diào)用中引用被調(diào)度的函數(shù)。調(diào)用CmtWaitForThreadPoolFunctionCompletion函數(shù)使得主線程等待線程池函數(shù)結(jié)束后再退出。如果主線程在輔助線程完成之前退出,那么可能會(huì)造成輔助線程不能正確地清理分配到的資源。這些輔助線程使用的庫(kù)也不會(huì)被正確的釋放掉。
使用異步定時(shí)器為了使用LabWindows/CVI的異步定時(shí)器在輔助線程中運(yùn)行代碼,需要調(diào)用Toolslib中的NewAsyncTimer函數(shù)。需要向函數(shù)傳遞在輔助線程中運(yùn)行的函數(shù)名稱和函數(shù)執(zhí)行的時(shí)間間隔。傳遞給NewAsyncTimer的函數(shù)被稱為異步定時(shí)器回調(diào)函數(shù)。異步定時(shí)器儀器驅(qū)動(dòng)程序會(huì)按照用戶指定的周期調(diào)用異步定時(shí)器回調(diào)函數(shù)。異步定時(shí)器回調(diào)函數(shù)的名稱是任意的,但是必須遵循下面的原型:
由于LabWindows/CVI的異步定時(shí)器儀器驅(qū)動(dòng)使用Windows多媒體定時(shí)器來(lái)實(shí)現(xiàn)異步定時(shí)器回調(diào)函數(shù),所以用戶可指定的最小間隔是隨使用的計(jì)算機(jī)不同而變化的。如果用戶指定了一個(gè)比系統(tǒng)可用的最大分辨率還小的時(shí)間間隔,那么可能會(huì)產(chǎn)生不可預(yù)知的行為。不可預(yù)知的行為通常發(fā)生在設(shè)定的時(shí)間間隔小于10ms時(shí)。同時(shí),異步定時(shí)器儀器驅(qū)動(dòng)使用一個(gè)多媒體定時(shí)器線程來(lái)運(yùn)行單個(gè)程序中注冊(cè)的所有異步定時(shí)器回調(diào)函數(shù)。所以,如果用戶希望程序并行地執(zhí)行多個(gè)函數(shù),那么NI公司推薦使用LabWindows/CVI Utility Library中的線程池函數(shù)來(lái)代替異步定時(shí)器函數(shù)。
5. 保護(hù)數(shù)據(jù)在使用輔助線程的時(shí)候,程序員需要解決的一個(gè)非常關(guān)鍵的問(wèn)題是數(shù)據(jù)保護(hù)。在多個(gè)線程同時(shí)進(jìn)行訪問(wèn)時(shí),程序需要對(duì)全局變量、靜態(tài)局部變量和動(dòng)態(tài)分配的變量進(jìn)行保護(hù)。不這樣做會(huì)導(dǎo)致間歇性的邏輯錯(cuò)誤發(fā)生,而且很難發(fā)現(xiàn)。LabWindows/CVI提供了各種高級(jí)機(jī)制幫助用戶對(duì)受到并發(fā)訪問(wèn)的數(shù)據(jù)進(jìn)行保護(hù)。保護(hù)數(shù)據(jù)時(shí),一個(gè)重要的考慮就是避免死鎖。
如果一個(gè)變量被多個(gè)線程訪問(wèn),那么它必須被保護(hù),以確保它的值可靠。例如下面一個(gè)例子,一個(gè)多線程程序在多個(gè)線程中對(duì)全局整型counter變量的值進(jìn)行累加。
count = count + 1;
這段代碼按照下列CPU指令順序執(zhí)行的:
1.將變量值移入處理器的寄存器中
2.增加寄存器中的變量值
3.把寄存器中的變量值寫(xiě)回count變量
由于操作系統(tǒng)可能在線程運(yùn)行過(guò)程中的任意時(shí)刻打斷線程,所以執(zhí)行這些指令的兩個(gè)線程可能按照如下的順序進(jìn)行(假設(shè)count初始值為5):
線程1:將count變量的值移到寄存器中。(count=5,寄存器=5),然后切換到線程2(count=5,寄存器未知)。
線程2:將count變量的值移到寄存器中(count=5,寄存器=5)。
線程2: 增加寄存器中的值(count=5,寄存器=6)。
線程2: 將寄存器中的值寫(xiě)回count變量(count=6,寄存器=6),然后切換回線程1.(count=6,寄存器=5)。
線程1: 增加寄存器的值。(count=6,寄存器=6)。
線程1: 將寄存器中的值寫(xiě)回count變量(count= 6, register = 6)。
由于線程1在增加變量值并將其寫(xiě)回之前被打斷,所以變量count的值被設(shè)為6而不是7。操作系統(tǒng)為系統(tǒng)中地每一個(gè)線程的寄存器都保存了副本。即使編寫(xiě)了count++這樣的代碼,用戶還是會(huì)遇到相同的問(wèn)題,因?yàn)樘幚砥鲿?huì)將代碼按照多條指令執(zhí)行。注意,特定的時(shí)序狀態(tài)導(dǎo)致了這個(gè)錯(cuò)誤。這就意味著程序可能正確運(yùn)行1000次,而只有一次故障。經(jīng)驗(yàn)告訴我們,有著數(shù)據(jù)保護(hù)不當(dāng)問(wèn)題的多線程程序在測(cè)試的過(guò)程中通常是正確的,但是一到客戶安裝并運(yùn)行它們時(shí),就會(huì)發(fā)生錯(cuò)誤。
需要保護(hù)的數(shù)據(jù)類型只有程序中的多個(gè)線程可以訪問(wèn)到的數(shù)據(jù)是需要保護(hù)的。全局變量、靜態(tài)局部變量和動(dòng)態(tài)分配內(nèi)存位于通常的內(nèi)存空間中,程序中的所有線程都可以訪問(wèn)它們。多個(gè)線程對(duì)內(nèi)存空間中存儲(chǔ)的這些類型的數(shù)據(jù)進(jìn)行并發(fā)訪問(wèn)時(shí),必須加以保護(hù)。函數(shù)參數(shù)和非靜態(tài)局部變量位于堆棧上。操作系統(tǒng)為每個(gè)線程分配獨(dú)立的堆棧。因此,每個(gè)線程都擁有參數(shù)和非靜態(tài)局部變量的獨(dú)立副本,所以它們不需要為并發(fā)訪問(wèn)進(jìn)行保護(hù)。下面的代碼顯示了必須為并發(fā)訪問(wèn)而保護(hù)的數(shù)據(jù)類型。
通常說(shuō)來(lái),在多線程程序中保存數(shù)據(jù)需要將保存數(shù)據(jù)的變量與操作系統(tǒng)的線程鎖對(duì)象關(guān)聯(lián)起來(lái)。在讀取或者設(shè)定變量值的時(shí)候,需要首先調(diào)用操作系統(tǒng)API函數(shù)來(lái)獲取操作系統(tǒng)的線程鎖對(duì)象。在讀取或設(shè)定好變量值后,需要將線程鎖對(duì)象釋放掉。在一個(gè)特定的時(shí)間內(nèi),操作系統(tǒng)只允許一個(gè)線程獲得特定的線程鎖對(duì)象。一旦線程調(diào)用操作系統(tǒng)API函數(shù)試圖獲取另一個(gè)線程正在持有的線程鎖對(duì)象,那么試圖獲取線程鎖對(duì)象的線程回在操作系統(tǒng)API獲取函數(shù)中等待,直到擁有線程鎖對(duì)象的線程將它釋放掉后才返回。試圖獲取其它線程持有的線程鎖對(duì)象的線程被稱為阻塞線程。LabWindows/CVI Utility Library提供了三種保護(hù)數(shù)據(jù)的機(jī)制:線程鎖、線程安全變量和線程安全隊(duì)列。
線程鎖對(duì)操作系統(tǒng)提供的簡(jiǎn)單的線程鎖對(duì)象進(jìn)行了封裝。在三種情況下,你可能要使用到線程鎖。如果有一段需要訪問(wèn)多個(gè)共享數(shù)據(jù)變量的代碼,那么在運(yùn)行代碼前需要獲得線程鎖,而在代碼運(yùn)行后釋放線程鎖。與對(duì)每段數(shù)據(jù)都進(jìn)行保護(hù)相比,這個(gè)方法的好處是代碼更為簡(jiǎn)單,而且不容易出錯(cuò)。缺點(diǎn)是減低了性能,因?yàn)槌绦蛑械木€程持有線程鎖的時(shí)間可能會(huì)比實(shí)際需要的時(shí)間長(zhǎng),這會(huì)造成其它線程為獲得線程鎖而阻塞(等待)的時(shí)間變長(zhǎng)。使用線程鎖的另一種情況是需要對(duì)訪問(wèn)非線程安全的第三方庫(kù)函數(shù)時(shí)進(jìn)行保護(hù)。例如,有一個(gè)非線程安全的DLL用于控制硬件設(shè)備而你需要在多個(gè)線程中調(diào)用這個(gè)DLL,那么可以在線程中調(diào)用DLL前創(chuàng)建需要獲得的線程鎖。第三種情況是,你需要使用線程鎖來(lái)保護(hù)多個(gè)程序間共享的資源。共享內(nèi)存就是這樣一種資源。
線程安全變量技術(shù)將操作系統(tǒng)的線程鎖對(duì)象和需要保護(hù)的數(shù)據(jù)結(jié)合起來(lái)。與使用線程鎖來(lái)保護(hù)一段數(shù)據(jù)相比,這種方法更為簡(jiǎn)單而且不容易出錯(cuò)。你必須使用線程安全變量來(lái)保護(hù)所有類型的數(shù)據(jù),包括結(jié)構(gòu)體類型。線程安全變量比線程鎖更不容易出錯(cuò),是因?yàn)橛脩粜枰{(diào)用Utility Library API函數(shù)來(lái)訪問(wèn)數(shù)據(jù)。而API函數(shù)獲取操作系統(tǒng)的線程鎖對(duì)象,避免用戶不小心在未獲取OS線程鎖對(duì)象的情況下對(duì)數(shù)據(jù)進(jìn)行訪問(wèn)的錯(cuò)誤。線程安全變量技術(shù)比線程鎖更簡(jiǎn)單,因?yàn)橛脩糁恍枰褂靡粋€(gè)變量(線程安全變量句柄),而線程鎖技術(shù)則需要使用兩個(gè)變量(線程鎖句柄和需要保護(hù)的數(shù)據(jù)本身)。
線程安全隊(duì)列是一種在線程間進(jìn)行安全的數(shù)組數(shù)據(jù)傳遞的機(jī)制。在程序中有一個(gè)線程生成數(shù)組數(shù)據(jù)而另外一個(gè)線程對(duì)數(shù)組數(shù)據(jù)進(jìn)行處理時(shí),需要使用線程安全隊(duì)列。這類程序的一個(gè)例子就是在一個(gè)線程中采集數(shù)據(jù),而在另一個(gè)線程中分析數(shù)據(jù)或者將數(shù)據(jù)顯示在LabWindows/CVI的用戶界面上。與一個(gè)數(shù)組類型的線程安全變量相比,線程安全隊(duì)列有著如下的優(yōu)勢(shì):
- 線程安全隊(duì)列在其內(nèi)部使用了一種鎖策略,一個(gè)線程可以從隊(duì)列讀取數(shù)據(jù)而同時(shí)另一個(gè)線程向隊(duì)列中寫(xiě)入數(shù)據(jù)(例如,讀取和寫(xiě)入線程不會(huì)互相阻塞)。
- 用戶可以為基于事件的訪問(wèn)配置線程安全隊(duì)列。用戶可以注冊(cè)一個(gè)讀取回調(diào)函數(shù),在隊(duì)列中有一定數(shù)量的數(shù)據(jù)可用時(shí),調(diào)用這個(gè)函數(shù),并且/或者注冊(cè)一個(gè)寫(xiě)入回調(diào)函數(shù),在隊(duì)列中有一定的空間可用時(shí),調(diào)用這個(gè)函數(shù)。
- 用戶可以對(duì)線程安全隊(duì)列進(jìn)行配置,使得在數(shù)據(jù)增加而空間已滿時(shí),隊(duì)列可以自動(dòng)生長(zhǎng)。
在程序初始化的時(shí)候,調(diào)用CmtNewLock函數(shù)來(lái)為每個(gè)需要保護(hù)的數(shù)據(jù)集合創(chuàng)建線程鎖。這個(gè)函數(shù)返回一個(gè)句柄,用戶可以使用它在后續(xù)的函數(shù)調(diào)用中指定線程鎖。在訪問(wèn)由鎖保護(hù)的數(shù)據(jù)和代碼前,線程必須調(diào)用CmtGetLock函數(shù)來(lái)獲取線程鎖。在訪問(wèn)數(shù)據(jù)后,線程必須調(diào)用CmtReleaseLock函數(shù)來(lái)釋放線程鎖。在同一個(gè)線程中,可以多次調(diào)用CmtGetLock(不會(huì)對(duì)后續(xù)調(diào)用產(chǎn)生阻塞),但是用戶每一次調(diào)用CmtGetLock都需要調(diào)用一次CmtReleaseLock來(lái)釋放。在程序退出時(shí),調(diào)用CmtDiscardLock函數(shù)來(lái)釋放線程鎖資源。下面的代碼演示了如何使用LabWindows/CVI Utility Library中的線程鎖來(lái)保護(hù)全局變量。
線程安全變量技術(shù)將數(shù)據(jù)和操作系統(tǒng)線程鎖對(duì)象結(jié)合成為一個(gè)整體。這個(gè)方法避免了多線程編程中一個(gè)常見(jiàn)的錯(cuò)誤:程序員在訪問(wèn)變量時(shí)往往忘記首先去獲得鎖。這種方法還使得在函數(shù)間傳遞保護(hù)的數(shù)據(jù)變得容易,因?yàn)橹恍枰獋鬟f線程安全變量句柄而不需要既傳遞線程鎖句柄又要傳遞保護(hù)的變量。LabWindows/CVI Utility Library API中包含了幾種用于創(chuàng)建和訪問(wèn)線程安全變量的函數(shù)。利用這些函數(shù)可以創(chuàng)建任何類型的線程安全變量。因?yàn)椋瑐鬟f到函數(shù)中的參數(shù)在類型上是通用的,而且不提供類型安全。通常,你不會(huì)直接調(diào)用LabWindows/CVI Utility Library中的線程安全變量函數(shù)。
LabWindows/CVI Utility Library中的頭文件中包含了一些宏,它們提供了配合Utility Library函數(shù)使用的類型安全的封裝函數(shù)。除了提供類型安全,這些宏還幫助避免了多線程編程中的其它兩個(gè)常見(jiàn)錯(cuò)誤。這些錯(cuò)誤是在訪問(wèn)數(shù)據(jù)后忘記釋放鎖對(duì)象,或者是在前面沒(méi)有獲取鎖對(duì)象時(shí)試圖釋放鎖對(duì)象。使用DefineThreadSafeScalarVar和DefineThreadSafeArrayVar宏來(lái)創(chuàng)建線程安全變量和類型安全的函數(shù)供使用和訪問(wèn)。如果需要從多個(gè)源文件中訪問(wèn)線程安全變量,請(qǐng)?jiān)趇nclude(.h)文件中使用DeclareThreadSafeScalarVar或者DeclareThreadSafeArrayVar宏來(lái)創(chuàng)建訪問(wèn)函數(shù)的聲明。DefineThreadSafeScalarVar(datatype,VarName,maxGetPointerNestingLevel)宏創(chuàng)建以下訪問(wèn)函數(shù):
注意事項(xiàng):這些宏使用傳遞進(jìn)來(lái)的第二個(gè)參數(shù)(在這個(gè)例子中為VarName)作為標(biāo)識(shí)來(lái)為線程安全變量創(chuàng)建自定義的訪問(wèn)函數(shù)名稱。
注意事項(xiàng):maxGetPointerNestingLevel參數(shù)將在“檢測(cè)GetPointerToVarName不匹配調(diào)用”一節(jié)中進(jìn)行進(jìn)一步討論。
在第一次訪問(wèn)線程安全變量前首先調(diào)用一次(只在一個(gè)線程里)InitializeVarName函數(shù)。在程序中止前調(diào)用UninitializeVarName函數(shù)。如果需要對(duì)變量當(dāng)前的值進(jìn)行更改(如,增加一個(gè)整數(shù)的值),那么請(qǐng)調(diào)用GetPointerToVarName函數(shù),更改變量值,然后調(diào)用ReleasePointerToVarName函數(shù)。在同一個(gè)線程中,可以多次調(diào)用GetPointerToVarName函數(shù)(在后續(xù)的調(diào)用中不會(huì)發(fā)生阻塞),但是必須調(diào)用相同次數(shù)的ReleasePointerToVarName函數(shù)與GetPointerToVarName一一對(duì)應(yīng)。如果在相同的線程中,調(diào)用了ReleasePointerToVarName函數(shù),而前面沒(méi)有與之相匹配的GetPointerToVarName調(diào)用,那么ReleasePointerToVarName將會(huì)報(bào)告一個(gè)run-time error錯(cuò)誤。
如果需要對(duì)變量值進(jìn)行設(shè)定而不需要考慮其當(dāng)前值,那么請(qǐng)調(diào)用SetVarName函數(shù)。如果需要獲得變量的當(dāng)前值,請(qǐng)調(diào)用GetVarName函數(shù)。需要了解的一點(diǎn)是,在GetVarName從內(nèi)存中讀出變量值后而在其將變量值返回給你前,變量的值是有可能改變的。
下面的代碼顯示了如何使用線程安全變量作為前面例子中提到的計(jì)數(shù)變量。
使用數(shù)組作為線程安全變量
DefineThreadSafeArrayVar宏與DefineThreadSafeScalarVar宏相似,但是它還需要一個(gè)額外的參數(shù)來(lái)指定數(shù)組中元素的個(gè)數(shù)。同時(shí),與DefineThreadSafeScalarVar不同,DefineThreadSafeArrayVar沒(méi)有定義GetVarName和SetVarName函數(shù)。下面的聲明定義了有10個(gè)整數(shù)的線程安全數(shù)組。
DefineThreadSafeArrayVar (int, Array, 10, 0);
如果有多個(gè)彼此相關(guān)的變量,那么必須禁止兩個(gè)線程同時(shí)對(duì)這些變量進(jìn)行修改。例如,有一個(gè)數(shù)組和記錄數(shù)組中有效數(shù)據(jù)數(shù)目的count變量。如果一個(gè)線程需要?jiǎng)h除數(shù)組中的數(shù)據(jù),那么在另一個(gè)線程訪問(wèn)數(shù)據(jù)前,必須對(duì)數(shù)組和變量count值進(jìn)行更新。雖然可以使用單個(gè)LabWindows/CVI Utility Library線程鎖來(lái)對(duì)這兩種數(shù)據(jù)的訪問(wèn)保護(hù),但是更安全的做法是定義一個(gè)結(jié)構(gòu)體,然后使用這個(gè)結(jié)構(gòu)體作為線程安全變量。下面的例子顯示了如何使用線程安全變量來(lái)安全地向數(shù)組中填加一個(gè)數(shù)據(jù)。
可以通過(guò)DefineThreadSafeScalarVar和DefineThreadSafeArrayVar的最后一個(gè)參數(shù)(maxGetPointerNestingLevel),來(lái)指定最大數(shù)目的嵌套調(diào)用。通??梢园堰@個(gè)參數(shù)設(shè)為0,這樣GetPointerToVarName在檢測(cè)到同一線程中對(duì)GetPointerToVarName的兩次連續(xù)調(diào)用而中間沒(méi)有對(duì)ReleasePointerToVarName進(jìn)行調(diào)用時(shí),就會(huì)報(bào)出一個(gè)運(yùn)行錯(cuò)誤。例如,下面的代碼在第二次執(zhí)行的時(shí)候會(huì)報(bào)出run-time error的錯(cuò)誤,因?yàn)樗浟苏{(diào)用ReleasePointerToCount函數(shù)。
如果代碼中必須對(duì)GetPointerToVarName進(jìn)行嵌套調(diào)用時(shí),那么可將maxGetPointerNestingLevel參數(shù)設(shè)為一個(gè)大于零的整數(shù)。例如,下面的代碼將maxGetPointerNestingLevel參數(shù)設(shè)定為1,因此它允許對(duì)GetPointerToVarName進(jìn)行一級(jí)嵌套調(diào)用。
如果不知道GetPointerToVarName的最大嵌套級(jí)別,那么請(qǐng)傳遞TSV_ALLOW_UNLIMITED_NESTING來(lái)禁用對(duì)GetPointerToVarName函數(shù)的不匹配調(diào)用檢查。
線程安全隊(duì)列使用LabWindows/CVI Utility Library的線程安全隊(duì)列,可以在線程間安全地傳遞數(shù)據(jù)。當(dāng)需要用一個(gè)線程來(lái)采集數(shù)據(jù)而用另一個(gè)線程來(lái)處理數(shù)據(jù)時(shí),這種技術(shù)非常有用。線程安全隊(duì)列在其內(nèi)部處理所有的數(shù)據(jù)鎖定。通常說(shuō)來(lái),應(yīng)用程序中的輔助線程獲取數(shù)據(jù),而主線程在數(shù)據(jù)可用時(shí)讀取數(shù)據(jù)然后分析并/或顯示數(shù)據(jù)。下面的代碼顯示了線程如何使用線程安全隊(duì)列將數(shù)據(jù)傳遞到另外一個(gè)線程。在數(shù)據(jù)可用時(shí),主線程利用回調(diào)函數(shù)來(lái)讀取數(shù)據(jù)。
當(dāng)兩個(gè)線程同時(shí)等待對(duì)方持有的線程鎖定對(duì)象時(shí),代碼就不能繼續(xù)運(yùn)行了。這種狀況被稱為死鎖。如果用戶界面線程發(fā)生死鎖,那么它就不能響應(yīng)用戶的輸入。用戶必須非正常地結(jié)束程序。下面的例子解釋了死鎖是如何發(fā)生的。
線程1:調(diào)用函數(shù)來(lái)獲取線程鎖A(線程1:無(wú)線程鎖,線程2:無(wú)線程鎖)。
線程1:從獲取線程鎖的函數(shù)返回(線程1:持有線程鎖A,線程2:無(wú)線程鎖)。
切換到線程2:(線程1:持有線程鎖A,線程2:無(wú)線程鎖)。
線程2:調(diào)用函數(shù)來(lái)獲取線程鎖B(線程1:持有線程鎖A,線程2:無(wú)線程鎖)。
線程2:從獲取線程鎖的函數(shù)返回(線程1:持有線程鎖A,線程2:持有線程鎖B)。
線程2:調(diào)用函數(shù)來(lái)獲取線程鎖A(線程1:持有線程鎖A,線程2:持有線程鎖B)。
線程2:由于線程1持有線程鎖A而被阻塞(線程1:持有線程鎖A,線程2:持有線程鎖B)。
切換到線程1:調(diào)用函數(shù)來(lái)獲取線程鎖B(線程1:持有線程鎖A,線程2:持有線程鎖B)。
線程1:調(diào)用函數(shù)來(lái)獲取線程鎖B(線程1:持有線程鎖A,線程2:持有線程鎖B)。
線程1:由于線程2持有線程鎖A而被阻塞(線程1:持有線程鎖A,線程2:持有線程鎖B)。
與不對(duì)數(shù)據(jù)進(jìn)行保護(hù)時(shí)產(chǎn)生的錯(cuò)誤相似,由于程序運(yùn)行的情況不同導(dǎo)致線程切換的時(shí)序不同,死鎖錯(cuò)誤間歇性地發(fā)生。例如,如果直到線程1持有線程鎖A和B后才切換到線程2,那么線程1就可以完成工作而釋放掉這些線程鎖,讓線程2在晚些時(shí)候獲取到。就像上面所說(shuō)的那樣,死鎖現(xiàn)象只有在線程同時(shí)獲取線程鎖時(shí)才會(huì)發(fā)生。所以你可以使用簡(jiǎn)單的規(guī)則來(lái)避免這種死鎖。當(dāng)需要獲取多個(gè)線程鎖對(duì)象時(shí),程序中的每個(gè)線程都需要按照相同的順序來(lái)獲取線程鎖對(duì)象。下面的LabWindows/CVI Utility Library函數(shù)獲取線程鎖對(duì)象,并且返回時(shí)并不釋放這些對(duì)象。
- CmtGetLock
- CmtGetTSQReadPtr
- CmtGetTSQWritePtr
注意事項(xiàng):通常說(shuō)來(lái),不需要直接調(diào)用CmtGetTSVPtr函數(shù)。它是通過(guò)DeclareThreadSafeVariable宏創(chuàng)建的GetPtrToVarName函數(shù)調(diào)用的。因此,對(duì)于調(diào)用的GetPtrToVarName函數(shù)需要將它作為線程鎖對(duì)象獲取函數(shù)來(lái)對(duì)待,應(yīng)該注意死鎖保護(hù)的問(wèn)題。
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ù)可以獲取線程鎖對(duì)象但在返回時(shí)并不釋放這些對(duì)象。注意,這不是完整的列表。
- EnterCriticalSection
- CreateMutex
- CreateSemaphore
- SignalObjectAndWait
- WaitForSingleObject
- MsgWaitForMultipleObjectsEx
在把一個(gè)函數(shù)調(diào)度到獨(dú)立的線程中運(yùn)行時(shí),需要對(duì)被調(diào)度函數(shù)的運(yùn)行狀態(tài)進(jìn)行監(jiān)視。為了獲得被調(diào)度函數(shù)的運(yùn)行狀態(tài),調(diào)用CmtGetThreadPoolFunctionAttribute來(lái)獲得ATTR_TP_FUNCTION_EXECUTION_STATUS屬性的值。也可以注冊(cè)一個(gè)回調(diào)函數(shù),線程池調(diào)用之后立即運(yùn)行被調(diào)度的函數(shù)和/或開(kāi)始運(yùn)行后立即由線程池調(diào)用。如果需要注冊(cè)這樣的回調(diào)函數(shù),必須使用CmtScheduleThreadFunctionAdv來(lái)對(duì)函數(shù)進(jìn)行調(diào)度。
通常說(shuō)來(lái),輔助進(jìn)程需要在主線程結(jié)束程序前完成。如果主線程在輔助線程完成之前結(jié)束,那么輔助線程將不能夠?qū)⒎峙涞降馁Y源清理掉。同時(shí),可能導(dǎo)致這些輔助線程所使用的庫(kù)函數(shù)也不能被正確清除。
可以調(diào)用CmtWaitForThreadPoolFunctionCompletion函數(shù)來(lái)安全地等待輔助線程結(jié)束運(yùn)行,然后允許主線程結(jié)束。
在一些例子中,輔助線程函數(shù)必須持續(xù)完成一些工作直到主線程讓它停止下來(lái)。在這類情況下,輔助線程通常在while循環(huán)中完成任務(wù)。while循環(huán)的條件是主線程中設(shè)定的整數(shù)變量,當(dāng)主線程需要告知輔助線程停止運(yùn)行時(shí),將其設(shè)為非零整數(shù)。下面的代碼顯示了如何使用while循環(huán)來(lái)控制輔助線程何時(shí)結(jié)束執(zhí)行。
注意事項(xiàng):如果使用volatile關(guān)鍵字,這段代碼在經(jīng)過(guò)優(yōu)化的編譯器(如Microsoft Visual C++)后功能是正常的。優(yōu)化的編譯器確定while循環(huán)中的代碼不會(huì)修改quit變量的值。因此,作為優(yōu)化,編譯器可能只使用quit變量在while循環(huán)條件中的初始值。使用volatile關(guān)鍵字是告知編譯器另一個(gè)線程可能會(huì)改變quit變量的值。這樣,編譯器在每次循環(huán)運(yùn)行時(shí)都使用更新過(guò)后的quit變量值。
有些時(shí)候,當(dāng)主線程進(jìn)行其他任務(wù)的時(shí)候需要暫停輔助線程的運(yùn)行。如果你暫停正在運(yùn)行操作系統(tǒng)代碼的線程,可能會(huì)使得操作系統(tǒng)處于非法狀態(tài)。因此,在需要暫停的線程中需要始終調(diào)用Windows SDK的SuspendThreadfunction函數(shù)。這樣,可以確保線程在運(yùn)行關(guān)鍵代碼時(shí)不被暫停。在另一個(gè)線程中調(diào)用Windows SDK的ResumeThreadfunction是安全的。下面的代碼展示了如何使用它們。
在Windows操作系統(tǒng)中,可以指定每個(gè)進(jìn)程和線程工作的相對(duì)重要性(被稱為優(yōu)先級(jí))。如果給予進(jìn)程或線程以較高的優(yōu)先級(jí),那么它們將獲得比優(yōu)先級(jí)較低的線程更好的優(yōu)先選擇。這意味著當(dāng)多個(gè)線程需要運(yùn)行的時(shí)候,具有最高優(yōu)先級(jí)的線程首先運(yùn)行。
Windows將優(yōu)先級(jí)分類。同一進(jìn)程中的所有線程擁有相同的優(yōu)先級(jí)類別。同一進(jìn)程中的每個(gè)線程都有著與進(jìn)程優(yōu)先級(jí)類別相關(guān)的優(yōu)先級(jí)??梢哉{(diào)用Windows SDK中的SetProcessPriorityClass函數(shù)來(lái)設(shè)定系統(tǒng)中線程的優(yōu)先級(jí)。
NI公司不推薦用戶將線程的優(yōu)先級(jí)設(shè)為實(shí)時(shí)優(yōu)先級(jí),除非只在很短時(shí)間內(nèi)這樣做。當(dāng)進(jìn)程被設(shè)為實(shí)時(shí)優(yōu)先級(jí)時(shí),它運(yùn)行時(shí)系統(tǒng)中斷會(huì)被阻塞。這會(huì)造成鼠標(biāo)、鍵盤、硬盤及其它至關(guān)重要的系統(tǒng)特性不能工作,并很可能造成系統(tǒng)被鎖定。
如果你是使用CmtScheduleThreadFunctionAdv函數(shù)來(lái)將函數(shù)調(diào)度到線程池中運(yùn)行,那么還可以指定執(zhí)行所調(diào)度函數(shù)的線程的優(yōu)先級(jí)。線程池在運(yùn)行被調(diào)度的函數(shù)前會(huì)改變線程優(yōu)先級(jí)。在函數(shù)結(jié)束運(yùn)行后,線程池會(huì)將線程優(yōu)先級(jí)恢復(fù)到原來(lái)的優(yōu)先級(jí)。可使用CmtScheduleThreadFunctionAdv函數(shù)來(lái)在默認(rèn)的和自定義的線程池中指定線程的優(yōu)先級(jí)。
在創(chuàng)建自定義的LabWindows/CVI Utility Library線程池(調(diào)用CmtNewThreadPool函數(shù))時(shí),可以設(shè)定池中各線程的默認(rèn)優(yōu)先級(jí)。
9. 消息處理每個(gè)創(chuàng)建了窗口的線程必須對(duì)Windows消息進(jìn)行處理以避免系統(tǒng)鎖定。用戶界面庫(kù)中的RunUserInterfacefunction函數(shù)包含了處理LabWindows/CVI用戶界面事件和Windows消息的循環(huán)。用戶界面庫(kù)中的GetUserEvent和ProcessSystemEventsfunctions函數(shù)在每次被調(diào)用時(shí)對(duì)Windows消息進(jìn)行處理。如果下列情況中的之一被滿足,那么程序中的每個(gè)線程都需要調(diào)用GetUserEventor和ProcessSystemEventsregularly函數(shù)來(lái)處理Windows消息。
- 線程創(chuàng)建了窗口但沒(méi)有調(diào)用RunUserInterface函數(shù)。
- 線程創(chuàng)建了窗口并調(diào)用了RunUserInterface函數(shù),但是在返回到RunUserInterface循環(huán)前需要運(yùn)行的回調(diào)函數(shù)占用了大量時(shí)間(多于幾百毫秒)。
但是,在代碼中的某些地方不適合用于處理Windows消息。在LabWindows/CVI的用戶界面線程中調(diào)用了GetUserEvent、ProcessSystemEvents或RunUserInterface函數(shù)時(shí),線程可以調(diào)用一個(gè)用戶界面回調(diào)函數(shù)。如果在用戶界面回調(diào)函數(shù)中調(diào)用這些函數(shù)之一,那么線程將調(diào)用另外一個(gè)回調(diào)函數(shù)。除非需要這樣做,否則這種事件將產(chǎn)生不可預(yù)知的行為。
Utility Library中的多線程函數(shù)會(huì)造成線程在循環(huán)中等待,允許你指定是否在等待線程中對(duì)消息進(jìn)行處理。例如,CmtWaitForThreadPoolFunctionCompletion函數(shù)中有個(gè)Option參數(shù),可以使用它來(lái)指定處理Windows消息的等待線程。
有的時(shí)候,線程對(duì)窗口的創(chuàng)建不是那么顯而易見(jiàn)的。用戶界面庫(kù)函數(shù)如LoadPanel、CreatePanel和FileSelectPopup等都創(chuàng)建了用于顯示和丟棄的窗口。這些函數(shù)還為每個(gè)調(diào)用它們的線程創(chuàng)建了隱藏的窗口。在銷毀可見(jiàn)的窗口時(shí),這個(gè)隱藏的窗口并沒(méi)有被銷毀。除了這些用戶界面庫(kù)函數(shù)外,各種其它的LabWindows/CVI庫(kù)函數(shù)和Windows API函數(shù)創(chuàng)建了隱藏的背景窗口。為了避免系統(tǒng)的鎖定,必須在線程中對(duì)使用這兩種方法創(chuàng)建的窗口的Windows消息進(jìn)行處理。
10. 使用線程局部變量線程局部變量與全局變量相似,可以在任意線程中對(duì)它們進(jìn)行訪問(wèn)。但是,全局變量對(duì)于所有線程只保存一個(gè)值,而線程局部變量為每個(gè)訪問(wèn)的線程保存一個(gè)獨(dú)立的值。當(dāng)程序中需要同時(shí)在多個(gè)上下文中進(jìn)行相同的任務(wù),而其中每個(gè)上下文都對(duì)應(yīng)一個(gè)獨(dú)立的線程時(shí),通常需要使用到線程局部變量。例如,你編寫(xiě)了一個(gè)并行的測(cè)試程序,其中的每個(gè)線程處理一個(gè)待測(cè)單元,那么你可能需要使用線程局部變量來(lái)保存每個(gè)單元的特定信息(例如序列號(hào))。
Windows API提供了用于創(chuàng)建和訪問(wèn)線程局部變量的機(jī)制,但是該機(jī)制對(duì)每個(gè)進(jìn)程中可用的線程局部變量的數(shù)目進(jìn)行了限定。LabWindows/CVI Utility Library中的線程局部變量函數(shù)沒(méi)有這種限制。下面的代碼展示了如何創(chuàng)建和訪問(wèn)一個(gè)保存了整數(shù)的線程局部變量。
如果你使用線程局部變量來(lái)存儲(chǔ)動(dòng)態(tài)分配到的資源,那么你需要釋放掉分配的資源的每一個(gè)拷貝。也就是說(shuō),你需要釋放掉每個(gè)線程中分配到的資源拷貝。使用LabWindows/CVI的線程局部變量,你可以指定用于銷毀線程局部變量的回調(diào)函數(shù)。當(dāng)你銷毀線程局部變量時(shí),每個(gè)訪問(wèn)過(guò)變量的線程都會(huì)調(diào)用指定的回調(diào)函數(shù)。下面的代碼展示了如何創(chuàng)建和訪問(wèn)保存了動(dòng)態(tài)分配的字符串的線程局部變量。
一些分配的資源必須在分配到它們的線程中釋放。這些資源被稱為擁有線程關(guān)聯(lián)度。例如,面板必須在創(chuàng)建它的線程中銷毀掉。在調(diào)用CmtDiscardThreadLocalVar時(shí),Utility Library在線程中調(diào)用被稱為CmtDiscardThreadLocalVar的線程局部變量銷毀回調(diào)函數(shù)。Utility Library為每一個(gè)訪問(wèn)過(guò)該變量的線程調(diào)用一次銷毀回調(diào)函數(shù)。它將threadID參數(shù)傳遞給銷毀回調(diào)函數(shù),這個(gè)參數(shù)指定了調(diào)用銷毀回調(diào)函數(shù)的線程的ID號(hào)。你可以使用這個(gè)線程ID來(lái)確定是否可以直接釋放掉擁有線程關(guān)聯(lián)度的資源還是必須在正確的線程中調(diào)用Toolslib中的PostDeferredCallToThreadAndWait函數(shù)來(lái)釋放資源。下面的代碼顯示了如何更改前面的例子以在分配字符串的線程中將它們釋放掉。
使用LabWindows/CVI中的一些庫(kù),你可以在系統(tǒng)創(chuàng)建的線程中接收回調(diào)函數(shù)。因?yàn)檫@些庫(kù)會(huì)自動(dòng)創(chuàng)建執(zhí)行回調(diào)函數(shù)的線程,所以你不需要?jiǎng)?chuàng)建線程或者將函數(shù)調(diào)度到單獨(dú)的線程中執(zhí)行。在程序中,你仍然需要對(duì)這些線程和其它線程間共享的數(shù)據(jù)進(jìn)行保護(hù)。這些回調(diào)函數(shù)的實(shí)現(xiàn)通常被稱為是異步事件。
LabWindows/CVI的GPIB/GPIB 488.2庫(kù)中,可以調(diào)用ibnotify來(lái)注冊(cè)事件發(fā)生時(shí)GPIB/GPIB 488.2庫(kù)調(diào)用的回調(diào)函數(shù)。你可以為每一個(gè)電路板或器件指定一個(gè)回調(diào)函數(shù)。可以為事件指定調(diào)用的回調(diào)函數(shù)。GPIB/GPIB 488.2庫(kù)會(huì)創(chuàng)建用于執(zhí)行回調(diào)函數(shù)的線程。
在LabWindows/CVI的虛擬儀器軟件構(gòu)架 (VISA) 庫(kù)中,你可以調(diào)用viInstallHandler函數(shù)來(lái)注冊(cè)多個(gè)事件句柄(回調(diào)函數(shù))用于在特定的ViSession中接收VISA事件(I/O完成、服務(wù)請(qǐng)求等等)類型。VISA庫(kù)通常創(chuàng)建獨(dú)立的線程來(lái)執(zhí)行回調(diào)函數(shù)。VISA可能會(huì)對(duì)一個(gè)進(jìn)程中的所有回調(diào)函數(shù)使用同一個(gè)線程,或者對(duì)每個(gè)ViSession使用單獨(dú)的線程。你需要為某個(gè)指定的事件類型調(diào)用viEnableEvent函數(shù)以通知VISA庫(kù)調(diào)用已注冊(cè)的事件句柄。
在LabWindows/CVI VXI庫(kù)中,每個(gè)中斷或回調(diào)函數(shù)類型都有自己的回調(diào)注冊(cè)和使能函數(shù)。例如,為了接收NI-VXI中斷,你必須調(diào)用SetVXIintHandler和EnableVXIint函數(shù)。VXI庫(kù)使用自己創(chuàng)建的獨(dú)立線程來(lái)執(zhí)行回調(diào)函數(shù)。對(duì)于同一進(jìn)程中所有的回調(diào)函數(shù),VXI都使用相同的線程。
13. 為線程設(shè)定首選的處理器可以使用平臺(tái)SDK中的SetThreadIdealProcessor函數(shù)來(lái)指定執(zhí)行某一線程的處理器。這個(gè)函數(shù)的第一個(gè)參數(shù)是線程句柄。第二個(gè)參數(shù)是以零為索引起始的處理器??梢哉{(diào)用LabWindows/CVI Utility Library中的CmtGetThreadPoolFunctionAttribute函數(shù),使用ATTR_TP_FUNCTION_THREAD_HANDLE屬性來(lái)獲取線程池線程的句柄??梢哉{(diào)用LabWindows/CVI Utility Library中的CmtGetNumberOfProcessors函數(shù)來(lái)通過(guò)程序來(lái)確定運(yùn)行該程序的計(jì)算機(jī)上處理器的數(shù)量。
可以使用平臺(tái)SDK中的SetProcessAffinityMask函數(shù)來(lái)指定允許執(zhí)行你的程序的處理器??梢允褂闷脚_(tái)SDK中的SetThreadAffinityMask函數(shù)來(lái)指定允許執(zhí)行程序中特定線程的處理器。傳遞到SetThreadAffinityMask中的mask變量必須是傳遞到SetProcessAffinityMask中的mask變量的子集。