古詩詞大全網 - 古詩大全 - MPI 中多線程的使用

MPI 中多線程的使用

在 上壹篇 中我們介紹了 MPI-3 中***享內存操作,下面我們將介紹 MPI 中多線程的使用,以助於我們理解 MPI-3 中引進的線程安全的 Mprobe 操作(將在 下壹篇 中介紹)。

通常 MPI 中大多數操作的基本實體是進程,但是MPI 進程中可以執行多個線程。

進程是操作系統動態執行的基本單元,在傳統的操作系統中,進程既是基本的分配單元,也是基本的執行單元。通常在壹個進程中可以包含若幹個線程,壹個進程中至少有壹個線程。線程可以利用進程所擁有的資源,在引入線程的操作系統中,通常都是把進程作為分配資源的基本單位,而把線程作為獨立運行和獨立調度的基本單位,由於線程比進程粒度更小,基本上不擁有系統資源,故對它的調度所付出的開銷就會小得多,能更高效的提高系統多個程序間並發執行的程度。

進程和線程的主要差別在於它們是不同的操作系統資源管理方式。進程有獨立的地址空間,而線程只是壹個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但線程沒有獨立的地址空間。

MPI 進程中可以執行多個線程,同壹個進程的多個線程具有均等的機會參與該進程的 MPI 通信。

某些情況下在 MPI 中使用多線程能夠提供很大的方便,如:

在單線程的情況下,壹個進程無法安全地執行到其自身的阻塞點到點通信。采用多線程,則可使同壹個進程的兩個線程分別執行阻塞發送和阻塞接收而不至於死鎖。

MPI 要求 MPI_Init 和 MPI_Finalize 調用必須在相同線程內配對執行,執行了這兩個操作的線程被稱作主線程(main thread),主線程的 MPI_Finalize 調用必須在所有其它線程都執行完 MPI 相關的通信、I/O 和其它 MPI 操作之後執行。

線程安全性是指多個線程可以同時執行消息傳遞的相關調用而不會相互影響。

MPI被設計為線程安全的,但應用程序自身負責維護多線程安全,壹個簡單的辦法是在不同線程使用不同的通信子對象,這樣可實現線程間操作的互不幹擾。MPI 的大多數操作都滿足線程安全性的條件,但是也有例外,如在多線程中使用 MPI.Comm.Probe 或 MPI.Comm.Iprobe 來確定壹個消息的來源和大小,然後接收該消息的操作就不是線程安全的,不過 MPI-3 提供了線程安全的版本 MPI.Comm.Mprobe 和 MPI.Comm.Improbe,這些將在 下壹篇 中介紹。另外 MPI.File.Seek 也不是線程安全的,但是可以用線程安全的顯式偏移文件操作函數,如 MPI.File.Read_at 等來替代其操作。

該函數除了實施正常由 MPI.Init 執行的初始化之外,還負責初始化 MPI 多線程執行環境。 reauired 參數指出所要求的多線程支持程度,可能的取值如下:

這些常數都是整數,並且在數值上是從小到大的。

該函數返回 MPI 環境實際支持的多線程級別。

MPI.COMM_WORLD 中不同進程可分別設置不同的線程支持級別。多線程 MPI 中調用 MPI.Init 的實際效果等價於用 MPI.THREAD_SINGLE 調用 MPI.Init_thread。

註意 :使用 mpi4py 時,MPI.Init 和 MPI.Init_thread 在從 mpi4py 包中 import MPI 時會被自動調用,因此壹般不用在程序中顯式調用。mpi4py 中 MPI.Init_thread 默認 required 的線程級別是 MPI.THREAD_MULTIPLE,但實際得到的線程級別由 MPI 環境給出。如果想要手動控制 MPI 程序的初始化或者設置 MPI 線程級別,可以在 import MPI 之前先 import rc 模塊,並設置 rc.initialize = False,然後手動初始化,或者設置 rc.thread_level 為需要的級別("multiple", "serialized", "funneled" 或 "single")。

返回 MPI 環境支持的線程級別。

判斷調用該函數的線程是否為主線程,如果是,返回 True,否則返回 False。

MPI 並沒有提供創建線程的函數或方法,創建線程需由其它的工具來完成,這就保證了 MPI 程序可以使用任何與 MPI 實現兼容的線程,而不局限於某壹種特定的線程。比較廣泛使用的線程工具有 Pthreads 庫和 OpenMP 庫。

在 Python 中則可以使用 thread 模塊(Python 3 中被命名為 _thread),但是更常用的是建立在其之上的更易用更高級別的線程模塊 threading 。下面對 threading 模塊作簡要的介紹。

threading 模塊提供了若幹函數和對象,這裏主要介紹 threading.Thread 對象。該對象表示壹個單獨運行的線程活動,可以通過兩種方式來創建和運行壹個單獨的線程:為其傳遞壹個可以被調用的對象;或者繼承該類並重載 run() 方法。壹般在子類中只能重載 __init__() 和 run() 方法,其它方法都不應該被重載。

當壹個線程被創建後,必須使用 start() 方法來啟動該線程,該方法會在內部調用 run() 方法。

壹個線程被啟動後,它的狀態會是 "alive",直到該線程的 run() 方法運行停止(不管是正常結束還是異常中止)。可以用 is_alive() 方法來測試該線程是否 "alive"。

其它線程可以調用壹個線程的 join() 方法,此操作會阻塞該調用線程直到被調用線程停止。

每個線程都有壹個名稱,如果沒有設置,系統會為其分配壹個默認的名稱。可以在構造壹個線程時為其傳遞壹個指定的名稱,或者在運行過程中通過 name 屬性動態改變。

壹個線程可以被標記為守護線程(daemon),守護線程可以壹直運行而不阻塞主程序退出。如果壹個服務無法用壹種容易的方法來中斷線程,或者希望線程工作到壹半時中止而不會損失或破壞數據,對此服務,使用守護線程就很有用。可以通過設置線程的 daemon 屬性來標記壹個線程為守護線程。默認情況下線程不作為守護線程。

程序的初始線程為主線程,主線程不是守護線程。

初始化壹個線程目標。 group 初始應該設置為 None,目前沒有作用,為今後的擴展所保留, target 為壹個可被調用的對象, name 如果非 None,設置線程的名稱,默認名稱為 "Thread-N",N 為壹個數字, args 和 kwargs 為其它參數。

如果子類要重載此構造方法,必須首先調用基類的構造方法(Thread.__init__()),然後再做其它事情。

啟動該線程。壹個線程只能至多調用壹次該方法,否則會拋出 RuntimeError 異常。

線程的活動或工作。可以在子類中重載該方法以完成需要的工作。

等待線程停止。默認情況下調用該方法的線程會無限阻塞直到被調用線程停止,但是如果 timeout 參數設置為壹個浮點數,則只會等待 timeout 秒就返回,無論被調用線程是否中止。該方法總是返回 None,因此必須調用 is_alive() 方法來確定該調用的返回是由於被調用線程停止還是由於等待了 timeout 時間。

可以調用壹個線程的 join() 方法多次。

返回該線程是否 "alive"。

線程的名稱,為壹個字符串。

線程的標識符,為壹個非 0 整數。

壹半布爾值表示線程是否為守護線程。

下面給出使用例程。

運行結果如下:

以上介紹了 MPI 中多線程的使用,在 下壹篇 中我們將介紹 MPI-3 中線程安全的 Mprobe。