2012-03-21 62 views
11

假設我有一個程序,通過線程初始化的全局變量的使用,如下所示:安全傳遞只讀數據到一個新的線程

int ThreadParameter; 

// this function runs from the main thread 
void SomeFunction() { 
    ThreadParameter = 5; 

    StartThread(); // some function to start a thread 
    // at this point, ThreadParameter is NEVER modified. 
} 

// this function is run in a background worker thread created by StartThread(); 
void WorkerThread() { 
    PrintValue(ThreadParameter); // we expect this to print "5" 
} 

這些問題應該適用於任何通用處理器架構,一個可能會遇到。我希望解決方案是可移植的,而不是針對具有更強內存保證的體系結構,比如x86。

  1. 一般問題:儘管很普遍,但在所有處理器架構中這是否真的很安全?如何使它安全,如果不是?
  2. 全局變量不是volatile;是否可能會在StartThread()電話後重新排序並讓我清洗?如何解決這個問題?
  3. 假設計算機有兩個有自己的緩存的處理器。主線程在第一個處理器上運行,工作線程在第二個處理器上運行假設包含ThreadParameter的內存塊在程序開始運行之前已被分頁到每個處理器的緩存中SomeFunction()SomeFunction()寫入5ThreadParameter,它存儲在第一個處理器的緩存中,然後啓動工作線程,該線程在第二個處理器上運行。第二個處理器上的WorkerThread()將不會看到ThreadParameter的未初始化數據,而不是5的預期值,因爲第二個處理器中的內存頁尚未看到來自第一個處理器的更新?
  4. 如果需要不同的東西 - 如何處理這個問題,而不是簡單的int,我可以使用一個指向更復雜的數據類型的指針,這些數據類型不一定用於多線程環境?

如果我的擔憂是沒有根據的,那麼我爲什麼不需要擔心的具體原因是什麼?

+0

我假設SomeFunction會調用多個WorkerThreads。我認爲你的問題更像是:「同時讀會導致我一個問題?」 ....? – Alex 2012-03-21 20:51:36

+0

那麼,使用POSIX線程時,你可以使用['pthread_once()'](http://linux.die.net/man/3/pthread_once)解決全局變量初始化難題 - 我不確定這是怎麼翻譯的到你的「便攜式」的定義。 – 2012-03-21 20:55:01

+0

我想最好的解決方案是'boost:shared_mutex'並將只讀文件複製到每個線程中。 – Pubby 2012-03-21 21:02:15

回答

2

從你的描述,似乎你寫ThreadParameter(或其他一些數據結構)在啓動任何子線程之前,你將永遠不會再次寫入ThreadParameter ...它存在可以根據需要讀取,但在初始化之後再也不會更改;那是對的嗎?如果是這樣,那麼每次子線程想要讀取數據時,甚至是第一次都不需要使用任何線程同步系統調用(或處理器/編譯器原語)。

volatile的處理方式在某種程度上與編譯器相關;我知道,至少在Diab for PowerPC中,有一個關於volatile處理的編譯器選項:在每次讀/寫變量之後使用PowerPC EIEIO(或MBAR)指令,或者不要使用它...這個除了禁止與變量相關的編譯器優化之外。 (EIEIO/MBAR是PowerPC關於禁止處理器本身對I/O進行重新排序的指令,即指令之前的所有I/O必須在指令之後的任何I/O之前完成)。

從正確性/安全性的角度來看,將其聲明爲不穩定並沒有什麼壞處。但是從實用的角度來看,如果你在StartThread()之前初始化ThreadParameter足夠遠,那麼聲明它不是必須的(並且不這樣做會加速所有後續訪問)。幾乎任何實質性的函數調用(比如printf()或cout或任何系統調用等)都會發出比必要更多指令的指令,以確保處理器不會很久以前不會處理寫入調用StartThread()之前的ThreadParameter。實際上,StartThread()本身幾乎肯定會在有問題的線程實際啓動之前執行足夠的指令。所以我建議你不需要聲明它是不穩定的,即使在調用StartThread()之前立即初始化它也可能不會。

現在就您的問題,如果包含該變量的頁面在運行主線程的處理器執行初始化之前已經加載到兩個處理器的緩存中,會發生什麼情況:如果您使用的是常用的通用平臺使用類似的CPU時,硬件應該已經到位,以便爲您處理緩存一致性。在通用平臺上發生緩存一致性問題的地方,無論它們是否是多處理器,是當處理器具有單獨的指令&數據緩存並且您編寫自修改代碼時:寫入內存的指令與數據難以區分,因此CPU不會使指令緩存中的那些位置無效,因此指令緩存中可能會有陳舊的指令,除非您隨後使指令緩存中的這些位置無效(發佈您自己的特定於處理器的彙編指令,您可能不會允許根據您的操作系統和您的線程的特權級別執行操作,或者爲您的操作系統發出適當的緩存無效系統調用)。但是你所描述的不是自我修改的代碼,所以你在這方面應該是安全的。

您的問題1詢問如何在所有處理器架構中實現安全。那麼,正如我上面所討論的那樣,如果您使用的數據總線已正確橋接的類似處理器,則應該是安全的。專爲多處理器互連而設計的通用處理器具有總線監聽協議,用於檢測對共享內存的寫入......只要線程庫正確配置共享內存區域即可。如果您正在嵌入式系統中工作,您可能必須在您的BSP中自行配置...對於PowerPC,您需要查看MMU/BAT配置中的WIMG位;我不熟悉其他體系結構,以便爲您提供有關這些體系結構的指針。但是....如果你的系統是自制的,或者你的處理器不是那種類型,你可能無法指望兩個處理器能夠窺探彼此的寫入;請諮詢您的硬件人員以獲得建議。

+0

如果您只編寫ThreadParameter _once_,並且假設您使用的是行爲良好的語言和編譯器(C++),則儘可能晚地創建該變量,最好在初始化它時將其設置爲** const **。如果您將在主線程中修改ThreadParameter並期待子線程遵守該更改,請嘗試在該變量上添加等待條件。您將等待主線程在使用它之前編寫ThreadParameter,並且可以在子線程運行時檢查新的掛起更新。寫得很好的程序可以自行退出並使用更新後的值重新運行。 – 2012-03-22 18:17:35

3

當您創建新線程時,線程的構造與線程函數的開始同步。這意味着你很好 - 你在創建線程之前寫入ThreadParameter,並且線程在它們啓動後訪問它,所以你可以確定寫入發生在之前,因此線程可以保證看到正確的價值。

(需要編譯器,以確保線程之前完成所有的寫操作開始是新的線程中可見。)

1
  1. 是的,它是安全的。
  2. 不知道。也許:if(ThreadParameter = 5) StartThread();。但是,一般來說,儘量不要再猜測編譯器。
  3. 可能不是。如果您在編寫代碼時不得不擔心這樣的低級別細節,那麼控制程序在多核機器上如何執行的邏輯可能無法很好地發揮其作用。
  4. Boost是您在多線程環境中處理複雜類型的朋友。
相關問題