2011-05-30 13 views
9

使用pthread時,我可以在線程創建時傳遞數據。如何將數據傳遞到正在運行的線程

將新數據傳遞給已經運行的線程的正確方法是什麼?

我正在考慮做一個全局變量,並讓我的線程讀取。

謝謝

+7

再一次,人們試圖解決問題,因爲他們是初學者問題。儘量記住你自己曾經是初學者。 – 2011-05-30 00:59:59

回答

6

這肯定會奏效。基本上,線程只是共享相同內存空間的輕量級進程。全局變量存在於該內存空間中,可用於每個線程。

訣竅不在於讀者和作者一樣。如果你有一個簡單的全局內存塊,比如int,那麼分配給那個int可能是安全的。 Bt考慮一些更復雜的事情,比如struct。只是要明確,讓我們說我們有

struct S { int a; float b; } s1, s2; 

現在s1,s2struct S類型的變量。我們可以初始化它們

s1 = { 42, 3.14f }; 

,我們可以將它們分配

s2 = s1; 

但是,當我們將它們分配處理器不能保證完成一步到位分配到整個結構 - 我們說這是不是原子。現在讓我們想象一下兩個線程:

thread 1: 
    while (true){ 
     printf("{%d,%f}\n", s2.a, s2.b); 
     sleep(1); 
    } 

thread 2: 
    while(true){ 
     sleep(1); 
     s2 = s1; 
     s1.a += 1; 
     s1.b += 3.14f ; 
    } 

我們可以看到,我們所期待s2有值{42, 3.14}, {43, 6.28}, {44, 9.42} ....

但是我們看到的印刷可能是類似的東西

{42,3.14} 
{43,3.14} 
{43,6.28} 

{43,3.14} 
{44,6.28} 

等。問題在於,線程1可能在分配期間的任何時候獲得控制權並「看着」s2。

道德是,雖然全局內存是一種完全可行的方式來做到這一點,但您需要考慮到您的線程將跨越另一個線程的可能性。有幾種解決方案,基本的是使用信號量。信號量有兩個操作,混淆地命名爲PV

P只是等到一個變量爲0,然後繼續,向變量加1; V從變量中減去1。唯一特別的是他們這樣做原子 - 他們不能被打斷。

現在,你代碼

thread 1: 
    while (true){ 
     P(); 
     printf("{%d,%f}\n", s2.a, s2.b); 
     V(); 
     sleep(1); 
    } 

thread 2: 
    while(true){ 
     sleep(1); 
     P(); 
     s2 = s1; 
     V(); 
     s1.a += 1; 
     s1.b += 3.14f ; 
    } 

和你保證,你永遠不會有螺紋2個半完成的任務,而線程1正在試圖打印。

(P線程有信號燈,順便說一句。)

+3

@Charlie:對不起,但我完全不同意關於線程的措辭作爲「輕量級進程」。在許多操作系統中 - 包括許多unixoid系統(如BSD)和Windows--線程是運行代碼的實體,而進程只是線程,內存空間等的容器。現在,我意識到這可能是挑剔的,但我認爲這個區別非常重要,非常重要。 – 0xC0000022L 2011-05-30 00:42:13

+2

@SAD這在歷史和事實上都是錯誤的。進程是具有一個或多個線程的程序計數器的存儲空間。線程是一個「控制線程」,它包含一個程序計數器和一個堆棧。參見例如關於線程的Wiki文章。 http://en.wikipedia.org/wiki/Thread_(computer_science)人們現在很困惑,但是一本好的操作系統書會討論歷史並將其清除。 – 2011-05-30 00:56:26

+0

「線程只是共享相同內存空間的輕量級進程」的想法實際上是一個可怕的錯誤觀念,源於Linux上的「POSIX線程」的原始不可靠的不符合POSIX標準的實現,稱爲LinuxThreads。這與標準對「過程」和「線程」的定義/用法完全矛盾。 – 2011-05-30 01:13:38

2

全局變量是不好的開始,甚至與多線程編程變得更糟。相反,線程的創建者應該分配某種傳遞給pthread_create的上下文對象,該對象包含向線程傳遞信息以及從線程傳遞信息所需的任何緩衝區,鎖,條件變量,隊列等。

2

您將需要自己構建它。最典型的方法需要來自另一個線程的一些合作,因爲它會是一個奇怪的接口,用一些數據和代碼來執行它「中斷」一個正在運行的線程......這也會有一些與像POSIX信號或IRQs,這兩種都很容易在處理過程中在腳下拍攝自己,如果你沒有仔細考慮過它...(簡單的例子:你不能在信號處理程序中調用malloc,因爲你可能會在malloc中間被打斷,所以你可能會崩潰,而其訪問僅部分更新malloc的內部數據結構。)

典型的做法是讓你的線程創建程序基本上是一個事件循環。您可以構建一個隊列結構並將其作爲參數傳遞給線程創建例程。然後其他線程可以入隊,線程的事件循環將使它出隊並處理數據。請注意,這比全局變量(或全局隊列)更清潔,因爲它可以擴展爲具有多個這樣的隊列。

您需要在該隊列數據結構上進行一些同步。可以寫關於如何實現隊列結構同步的全書,但最簡單的事情會有鎖和信號量。修改隊列時,線程會鎖定。當等待某些東西被出隊時,消費者線程會等待一個由入隊者增加的信號量。實現一些機制來關閉消費者線程也是一個好主意。

3

幾十年來,我一直使用asveikau建議的消息傳遞,基於生產者 - 消費者隊列的通信機制,而沒有任何與多線程相關的問題。有一些優點:

1)隊列上傳遞的'threadCommsClass'實例通常可以包含線程完成其工作所需的所有內容 - 輸入數據的成員/ s,輸出數據的成員/ s,線程調用來完成工作,在某處放置任何錯誤/異常消息和一個'returnToSender(this)'事件來調用,以便通過一些線程安全的方式將所有內容返回給請求者,這是工作者線程不需要知道的。工作線程然後異步地運行一組完全封裝的數據,不需要鎖定。 'returnToSender(this)'可能會將對象排隊到另一個P-C隊列中,它可能會將它PostMessage到一個GUI線程,它可能會將對象釋放回池中或者僅僅處理它。無論它做什麼,工作者線程都不需要知道它。

2)請求線程不需要知道任何線程做了什麼工作 - 所有的請求者需要的是一個推送的隊列。在極端情況下,隊列另一端的工作線程可能會序列化數據並通過網絡將其傳遞給另一臺計算機,只有在收到網絡回覆時才調用returnToSender(this) - 請求者不需要知道這一點細節 - 只有工作已經完成。

3)通常可以爲「threadCommsClass」實例和隊列安排活得比兩個請求者線程和工作線程。當請求者或工作者被終止並處理()在另一個之前時,這極大地減輕了這些問題 - 因爲它們不直接共享數據,不會有AV /不管。這也吹走了所有那些'我無法阻止我的工作線程,因爲它阻塞在一個阻塞API'的問題 - 爲什麼要阻止它,如果它可以成爲孤兒,並留下去死,沒有可能寫入被釋放的東西?

4)線程池減小到一個行的for循環,創建幾個工作線程,並將它們傳遞相同的輸入隊列。

5)鎖定被限制爲隊列。應用程序中越多的互斥鎖,condVars,critical-sections和其他同步鎖,越難控制它,間歇性死鎖的機會越大,這是調試的噩夢。使用排隊消息(理想情況下),只有隊列類具有鎖。隊列類必須100%與多生產者/消費者,但這是一個類,而不是一個應用程序充滿了不協調的鎖定,(yech!)。

6)一種threadCommsClass可以隨時提出,任何地方,在任何線程中並壓入到隊列中。請求者代碼甚至不需要直接執行它,例如。對記錄器類方法的調用'myLogger.logString(「Operation completed successfully」);'可以將字符串複製到comms對象中,將其排隊到執行日誌寫入的線程並立即返回。然後由logger class線程來處理日誌數據,當日志數據出隊時它可以將其寫入日誌文件,它可能會在一分鐘後發現日誌文件由於網絡問題而無法訪問。它可能會決定日誌文件太大,將其歸檔並啓動另一個日誌文件。它可以將字符串寫入磁盤,然後將threadMessageClass實例PostMessage到GUI線程以顯示在終端窗口中,無論如何。對日誌請求線程無關緊要,它只是繼續進行,就像任何其他調用日誌記錄的線程一樣,對性能沒有顯着影響。

7)如果你需要殺死一個線程等待隊列中,而不是waiing爲OS殺死它的應用程序關閉的,只是對其進行排隊的消息告訴它teminate。

有一定的缺點:

1)推搡數據直接進入螺旋件,預示其運行並等待它完成更容易理解並會更快,假設線程沒有被每次創建。

2)真正的異步操作,其中線程排隊等待某些工作,稍後通過調用某個必須傳遞結果的事件處理程序返回它,對於用於單線程代碼的開發人員來說更難以處理並且通常需要狀態機類型設計,其中必須在threadCommsClass中發送上下文數據,以便在結果返回時採取正確的操作。如果偶爾發生請求者需要等待的情況,它可以在threadCommsClass中發送一個由returnToSender方法發送信號的事件,但這顯然比簡單地等待某個線程處理完成更復雜。

無論設計時,忘記了簡單的全局變量作爲其他海報說。在線程通信中有一些全局類型 - 我經常使用的是線程安全的threadCommsClass實例池(這只是一個預填充對象的隊列)。任何希望通信的線程都必須從池中獲取一個threadCommsClass實例,並將其加載並排隊。當通信完成後,最後使用它的線程將其釋放回池中。這種方法可以防止失控的new(),並允許我在測試過程中輕鬆監視池級別,而不需要任何複雜的內存管理器(我通常使用定時器每秒鐘將池級別轉換爲狀態欄)。泄漏的物體(水平下降)和雙重釋放的物體(水平上升)很容易檢測到,因此得到修復。

多線程可以是安全和提供幾乎保持/增強快感可擴展,高性能的應用程序,(幾乎:),但你必須裁員簡單的全局 - 像對待龍舌蘭 - 快速,現在很容易高,但你只知道他們明天就會頭昏腦脹。

祝你好運!

馬丁

相關問題