2010-10-05 16 views
6

到目前爲止,我認爲任何在「共享」對象上完成的操作(對於多線程都是通用的)必須使用「同步」來保護,無論如何。顯然,我錯了 - 在我最近學習的代碼中,有很多類(線程安全的,正如作者聲稱的那樣),並且其中只有一個類使用關鍵部分來處理幾乎所有的方法。德爾福線程 - 代碼的哪些部分需要保護/同步?

如何找到我的代碼的哪些部分/方法需要使用CriticalSection(或任何其他方法)保護,哪些不是?

到目前爲止,我還沒有在任何有趣的解釋/條/博客記迷迷糊糊中,所有的谷歌的結果是:

一)線程和GUI之間的同步的例子。從簡單的進度條到最複雜的過程,但仍然很明顯:每次訪問/修改GUI組件的屬性時,都要在「同步」中執行此操作。但僅此而已。

b)解釋關鍵部分,互斥鎖等的文章。只是一種不同的保護/同步方法。

c)非常非常簡單的線程安全類(線程安全堆棧或列表)的示例 - 它們都執行相同的操作 - 實現進入/離開關鍵部分並返回實際堆棧/列表指針的鎖定/解鎖方法鎖定。

現在我正在尋找解釋哪部分代碼應該受到保護

可能是代碼形式),但請不要爲我提供一個更「使用同步更新進度」 ...;)

謝謝!

回答

5

你在問一個非常普遍的問題的具體答案。

基本上除了UI操作的,你應該保護每一個共享內存/資源訪問以避免出現兩個潛在的競爭線程:

  • 同時
  • 嘗試讀取不一致的內存
  • 寫存儲器從多個線程同時使用相同的資源...直到資源是線程安全的。

通常,我認爲任何其他操作線程都是安全的,包括訪問不共享內存或不共享對象的操作。

例如,考慮該對象:

type 
    TThrdExample = class 
    private 
    FValue: Integer; 
    public 
    procedure Inc; 
    procedure Dec; 
    function Value: Integer; 
    procedure ThreadInc; 
    procedure ThreadDec; 
    function ThreadValue: Integer; 
    end; 

ThreadVar 
    ThreadValue: Integer; 

INC,DEC和價值是其在F值字段進行操作的方法。這些方法在使用某種同步機制保護它們之前不是線程安全的。它可以是用於Value函數的MultipleReaderExclusiveWriterSinchronizer和用於Inc和Dec方法的CriticalSection。

ThreadInc和ThreadDec方法操作ThreadValue變量,它定義爲ThreadVar,所以我認爲它是ThreadSafe,因爲它們訪問的內存不在線程之間共享......每個來自不同線程的調用都將訪問不同的內存地址。

如果您知道,按照設計,一個類只能用於一個線程或其他同步機制,您可以自由地考慮線程安全設計

如果你想要更具體的答案,我建議你嘗試一個更具體的問題。

此致敬禮。

編輯:也許有人說,整場是一個壞榜樣,因爲你可以考慮因此沒有需要保護它的整數運算英特爾/ Windows的原子...但我希望你的想法。

+0

我不想要更具體的答案。事實上,我甚至在尋找更一般的。我需要學習很多關於線程的知識,並且我正在尋找一個很好的「切入點」;)特別是自從我使用Delphi 6以來,從未見過ThreadVar語句... – migajek 2010-10-05 23:39:46

+0

順便說一句,應該不是第三點是「儘量不要使用...」? ;) – migajek 2010-10-05 23:40:20

+0

@ migajek:不,第三點是正確的。 jachguate在說兩個線程可能會做什麼,需要同步。 – 2010-10-05 23:45:00

2

您誤解了TThread.Synchronize方法。

TThread.Synchronize和TThread.Queue方法在主(GUI)線程的上下文中執行受保護的代碼。這就是爲什麼你應該使用Syncronize或Queue來更新GUI控件(比如progressbar) - 通常只有主線程才能訪問GUI控件。

關鍵部分不同 - 受保護的代碼在獲取關鍵部分的線程的上下文中執行,並且在前一個線程釋放關鍵部分之前,不允許其他線程獲取關鍵部分。

2

如果需要某些對象以原子方式更新,則使用臨界區。這意味着,他們必須始終完全更新或者根本不更新。他們絕不能在過渡狀態下進入。

例如,使用簡單的整數讀/寫不是這種情況。讀取整數的操作以及寫入操作都是原子操作:在處理器寫入過程中,您無法讀取整數,只能更新一半。永遠都是舊的價值或新的價值。

但是,如果你想增量整數原子,你不是一個,而是三個,你必須做一次操作:讀取舊值到處理器的高速緩存,增加它,並把它寫回內存。每個操作都是原子操作,但其中的三個操作都不是。

一個線程可能會讀取舊值(例如200),在緩存中將其增加5,同時另一個線程也可能讀取該值(仍爲200)。然後第一個線程回寫205,而第二個線程將其緩存值200遞增到203並寫回203,重寫205.兩個增量(+5和+3)的結果應該是208,但是由於非 - 操作的原始性。

所以在使用時的關鍵部分:

  1. 一個變量,設置的變量,或任何資源是從多個線程使用,需要用原子方式更新。
  2. 這不是本身的原子(例如,呼叫是由臨界區把守的函數體內部的功能,已經是一個原子操作)
0

如果使用消息傳遞進行線程之間的通信,那麼基本上可以完全忽略同步原語,因爲每個線程只訪問其內部結構和消息本身。實質上,這比使用同步原語更容易和更可伸縮的體系結構。