【机器视觉】 HDevelop语言基础(五)-多线程
00. 目錄
文章目錄
- 00. 目錄
- 01. 概述
- 02. 啟動線程
- 03. 等待子線程結束
- 04. HDevelop中線程的執(zhí)行
- 05. 監(jiān)視線程
- 06. 掛起和恢復線程
- 06. 附錄
01. 概述
HDevelop 語言支持主線程中的子線程并行執(zhí)行程序和調用算子。 一旦啟動,子線程由線程 ID 標識,該線程 ID 是一個取決于操作系統(tǒng)的整數(shù)進程號。 子線程的執(zhí)行獨立于它們啟動的線程。 因此,無法預測子線程執(zhí)行結束確切時間點。 如果要訪問一組線程返回的數(shù)據(jù),則需要顯式等待相應的線程完成。
HDevelop 默認將線程數(shù)限制為 20。 如果需要,可以在首選項中修改此數(shù)字。 完全限制并發(fā)線程數(shù)的主要原因是為了防止用戶由于編程錯誤而無意中生成大量線程。 在這種情況下,系統(tǒng)負載和內存消耗可能會增長得如此之高,以至于 HDevelop 可能會變得無響應。
請注意,線程數(shù)包括所有“活動”線程。 特別是它還包括已經(jīng)完成但仍在被變量引用的線程。 在調整線程限制時必須考慮到這一點。 有關線程生命周期的信息,另請參閱第 307 頁的“HDevelop 中的線程執(zhí)行”部分。
02. 啟動線程
要啟動一個新線程,請在相應的運算符或過程調用前加上 par_start 限定符:
par_start <ThreadID> : gather_data() ...此調用在后臺啟動假設過程gather_data() 作為新的子線程并繼續(xù)執(zhí)行后續(xù)程序行。 線程 ID 在變量 ThreadID 中返回,該變量必須在尖括號中指定。 與 HDevelop 不同,在 HDevEngine 中,給定的 ThreadID 僅在啟動線程的過程中有效。
請注意, par_start 不是實際的算子,而只是修改調用行為的限定符。 因此,無法在操作員窗口中選擇 par_start。
如果啟動新的子線程會超過配置的最大線程數(shù)(見上文),則會引發(fā)異常。
您還可以從算子窗口中將過程或算子調用作為子線程啟動。 為此,打開算子窗口底部的高級并行化選項部分,勾選復選框并輸入將保存線程 ID 的變量的名稱。 如果雙擊包含 par_start 限定符的程序,并行化選項也將顯示在孫子窗口中。 對于某些程序行(例如,注釋、聲明、循環(huán)或賦值),不支持 par_start,并且相應的選項在孫子窗口中也不可用。
支持在一個循環(huán)中啟動多個線程。 在這種情況下,需要收集線程 ID,以便以后可以引用所有線程:
ThreadIDs := [] for Index := 1 to 5 by 1 par_start <ThreadID> : gather_data() ThreadIDs := [ThreadIDs, ThreadID] endfor在容器變量中收集線程 ID 通常更方便:
for Index := 1 to 5 by 1 par_start <ThreadIDs.at(Index - 1)> : gather_data() endfor當子線程在輸出變量中返回數(shù)據(jù)時,必須特別小心。 特別是,當子線程仍在運行時,不得在其他線程中訪問輸出變量。 否則無法保證數(shù)據(jù)有效。
同樣,必須確保多個線程不會干擾它們的結果。 假設過程gather_data像上面一樣作為多個線程啟動,但在輸出控制變量中返回數(shù)據(jù):
for Index := 1 to 5 by 1 par_start <ThreadIDs.at(Index - 1)> : gather_data(Result) // BEWARE!!! endfor在上面的例子中,所有線程都將在同一個變量中返回它們的結果,這肯定不是預期的。 Result 的最終值將是最后完成的線程的(不可預測的)返回值,所有其他結果都將丟失。
這個問題的一個簡單解決方案是將返回的數(shù)據(jù)收集到一個容器變量中,如前面所示,帶有線程 ID:
for Index := 1 to 5 by 1 par_start <ThreadIDs.at(Index - 1)> : gather_data(Result.at(Index - 1)) endfor在這里,每次調用gather_data 都會在容器變量Result 的唯一ID中返回其結果。
03. 等待子線程結束
使用運算符 par_join 等待單個線程或一組線程的完成。 舉個例子,假設我們想調用一個在后臺執(zhí)行一些魔術計算并返回一個計數(shù)值作為結果的過程。 在隨后的程序行中,我們希望使用該數(shù)字進行進一步的計算。
par_start <ThreadID> : count_objects(num) ... for i := 1 to num by 1 // BEWARE: num might be uninitialized ... endfor僅僅依靠子線程足夠快很可能會失敗。 因此,需要事先顯式調用 par_join。
par_start <ThreadID> : count_objects(num) ... par_join(ThreadID) for i := 1 to num by 1 ... endfor請注意,在 HDevelop 中,并不嚴格要求使用 par_join,因為主線程將始終比子線程壽命更長。 但是,如果程序要在 HDevEngine(參見程序員指南,第 116 頁)中執(zhí)行或導出為編程語言,則省略它可能會導致問題。 同樣,如果要導出程序,對全局變量的訪問可能需要一些額外的同步。 鑒于上一節(jié)中的示例,使用以下幾行可以實現(xiàn)等待循環(huán)中啟動的所有線程完成。
convert_vector_to_tuple(ThreadIDs, Threads) par_join(Threads)請注意,線程 ID 已收集在容器變量中。 因此,要使 par_join 正常工作,必須轉換為元組。
par_join 運算符會阻止調用它的過程的進一步執(zhí)行,直到所有指定的線程都完成為止。 在隨后的程序行中,可以可靠地訪問來自相應線程的結果。
04. HDevelop中線程的執(zhí)行
一般情況下,HDevelop 中的線程只有在按 F5 后程序連續(xù)運行時才會并行執(zhí)行。 在所有其他執(zhí)行模式中,只有選定的線程被啟動,所有其他線程保持停止,除非明確的用戶交互推進它們的執(zhí)行。 活動斷點、停止指令、運行時錯誤或未捕獲的異常也會導致所有線程停止,以便可以評估它們的當前狀態(tài)。 此約定可實現(xiàn)明確定義的調試過程,因為它消除了來自其他線程的無法控制的副作用。 程序窗口中的任何編輯操作也會導致同時運行的程序停止。
線程不能在外部被“殺死”。 它們可以在算子調用之間或通過中止可中斷算子來停止。 如果任何線程在 HDevelop 嘗試停止程序執(zhí)行時執(zhí)行了無法中斷的長時間運行的算子,則狀態(tài)行中會顯示相應的消息,并且相應的線程將在算子完成后最終停止。
選擇線程
恰好其中一個線程是所謂的選定線程; 默認情況下,這對應于程序的主線程。 PC的位置、調用棧的狀態(tài)、變量窗口中變量的狀態(tài)都與選中的線程相關聯(lián)。 選定的線程可以自動更改為停止的線程,例如,通過斷點、停止指令、未捕獲的異常或繪制運算符。
如何選擇特定線程在第 308 頁的“檢查線程”一節(jié)中進行了描述。除連續(xù)執(zhí)行之外的所有運行模式僅適用于所選線程。 與所選線程無關的程序行將在程序窗口中顯示為灰色。
線程和即時編譯過程
程序可以作為編譯的字節(jié)碼執(zhí)行,而不是由 HDevelop 解釋器解釋。 這在第 43 頁的“即時編譯”一節(jié)中進行了描述。使用編譯過程調試線程化 HDevelop 程序時有一個顯著差異。 如果程序連續(xù)運行然后被停止(通過用戶操作或斷點/停止指令),則無法檢查已編譯過程(變量、PC)的當前狀態(tài)。 您仍然可以單步執(zhí)行過程調用,但這會導致重新執(zhí)行相應的線程,從而可能導致意外的副作用。 請注意,當首先單步進入線程調用時,這不是問題,因為在這種情況下,過程始終由 HDevelop 解釋器執(zhí)行。
線程的生命周期
一個線程只要它仍然被變量引用就存在,即使它的執(zhí)行已經(jīng)完成。 這需要在par_join指令中引用線程,或者回退對應線程的PC進行調試。 但是,如果在調用之前手動將 PC 設置回程序行,則線程的生命周期結束。 除此之外,當使用 F2 重置程序時,所有子線程的生命周期都會結束。 在線程的生命周期中,線程會列在“線程視圖/調用堆棧”窗口中,從中可以選擇和管理它。
已完成但仍處于“活動”狀態(tài)的線程可能會導致在稍后啟動新線程時無意中超出配置的線程數(shù)限制。 此外,如果在執(zhí)行新線程的同時“殺死”已完成的線程,則可能會對運行時行為產生負面影響。 要顯式“殺死”已完成的線程,只需重置引用其線程 ID 的變量即可,如下例所示。
for Index := 0 to 4 by 1 par_start <ThreadIDs.at(Index)> : gather_data() endfor ... convert_vector_to_tuple (ThreadIDs, Threads) par_join (Threads) ... ThreadIDs := {} Threads := []錯誤處理
每個線程可以指定自己的錯誤處理,例如,使用 dev_set_check(‘?give_error’)。 新的子線程從其父線程繼承錯誤處理模式。 使用 try … catch 的異常處理僅在線程內有效,即在主線程中不可能捕獲在子線程中拋出的異常。
05. 監(jiān)視線程
程序及其線程的當前執(zhí)行狀態(tài)顯示在組合線程視圖/調用堆棧窗口中。 選擇 執(zhí)行 。 線程視圖/調用堆棧或單擊工具欄中的(另請參閱線程視圖/調用堆棧(第 80 頁))。 窗口的上半部分列出所有現(xiàn)有線程,而下半部分顯示所選線程的調用堆棧。 為了說明與此窗口的交互,請考慮以下(愚蠢的)示例。
for Index:= 1 to 5 by 1 par_start <ThreadIDs.at(Index - 1)> : wait_seconds(Index) endfor wait_seconds(2) stop()按F5后,程序會啟動5個子線程,最終到達stop指令,有的子線程還在運行,有的子線程已經(jīng)結束了。 相應的線程視圖如圖 8.4 所示。 請注意,未完成的線程由于另一個線程而處于停止狀態(tài)(在這種情況下是由主線程中的停止指令引起的)。
線程視圖在一個表中列出了所有線程的屬性。 每個線程第一列中的狀態(tài)圖標可視化當前執(zhí)行狀態(tài)。 當前選定的線程 (1) 由狀態(tài)圖標中的黃色箭頭標記,并以粗體文本突出顯示。 其他五個線程是從主線程開始的子線程。
要選擇另一個線程,請在線程視圖中雙擊它。 這還將根據(jù)所選線程更新 PC、調用堆棧和變量。 所選線程的活動過程將顯示在程序窗口中。
單步執(zhí)行包含 par_start 的程序行將初始化相應的線程而不實際啟動它。 要調試特定線程,請在 PC 位于相應的 par_start 行時按 F7。 這將自動使新子線程成為選定線程。 如果 PC 已經(jīng)通過調用行,首先在線程視圖窗口中選擇線程。 這將自動在程序窗口中顯示正確的過程,如果線程是由過程調用啟動的,則 PC 位于第一行。 對于由上例中的操作符調用啟動的線程,PC 將位于相應的調用上,而程序的其余部分將變灰(見圖 8.5)。 程序窗口中的通知行 (1) 顯示所選子線程的線程 ID,并允許快速訪問線程視圖窗口 (2)。 單擊 (3) 切換回主線程。
06. 掛起和恢復線程
線程可以在線程視圖窗口中顯式掛起和恢復。 掛起線程僅在算子調用之間起作用,即,如果線程當前正在運行,則在線程凍結之前仍將執(zhí)行當前算子。
要掛起線程,請右鍵單擊線程條目并選擇掛起線程。 掛起相當于在其當前狀態(tài)下被“凍結”,并將推遲后續(xù)運行命令,直到線程再次恢復,即運行命令將更改掛起線程的運行狀態(tài),但將阻止實際執(zhí)行。
要再次恢復掛起的線程,請右鍵單擊線程條目并選擇恢復線程。
06. 附錄
6.1 機器視覺博客匯總
網(wǎng)址:https://dengjin.blog.csdn.net/article/details/116837497
總結
以上是生活随笔為你收集整理的【机器视觉】 HDevelop语言基础(五)-多线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【机器视觉】 HDevelop语言基础(
- 下一篇: 【机器视觉】 HDevelop语言基础(