2013-06-20 19 views
2

當多個線程通過get/set-functions訪問屬性/字段時,是否有必要鎖定getter和setter?C#是否在必要的getter和setter中鎖定?

例如:

你有一個計時器,定期獲取對象的值。定期更新此值的過程。

public class Task : IProgressProvider 
{ 
    ... 
    public Progress GetProgress() 
    { 
     // should i lock here? 
     return _progress; 
    } 

    public SetProgress(Progress progress) 
    { 
     // and lock here? 
     _progress = progress; 
    } 

    public void Execute() 
    { 
     // ... 
     SetProgress(new Progress(10)); 
     // ... 
     SetProgress(new Progress(50)); 
     // ... 
     SetProgress(new Progress(100)); 
    } 
} 

public class ProgressReporter 
{ 
    public ProgressReporter() 
    { 
     _timer = new Timer(); 
     _timer.Elapsed += Elapsed; 
     // ... 
    } 

    // ... 

    public void Elapsed(...) 
    { 
     var progress = _task.GetProgress(); 
     // ... 
    } 
} 

的又一個問題:

  • 我應該鎖定在GetProgress和SetProgress功能。
  • 如果是,爲什麼?
  • 是揮發性的必要在這種情況下(假設一個或多個處理器核)
  • 爲了保持它的「簡單」讓我們假設屬性/從進展字段是隻讀的。 編輯:我知道+ =等等都沒有原子操作的,需要妥善處理指標(如鎖定)

我認爲設置和變量_progress閱讀是一個原子操作。 GetProgress將獲得非常當前的值並不重要。如果不是這次,它會在GetProgress的下一個調用中得到它。

+1

如果'_progress'是引用類型,然後讀取和寫入它的值(即改變基準)確實是一個原子操作,這樣你就不會需要鎖定在你的榜樣的特定代碼。但是如果你在setter或getter中改變了多個字段,或者這個字段不是一個帶有原子讀/寫的類型(例如'double'),那麼你需要鎖定。 –

+0

@MthetheWWatson:原子只是方程的一部分。如果您希望所有線程都讀取最近寫入的值,則可能需要鎖定。 (或顯式內存屏障。) –

+0

與http://blogs.msdn.com/b/ericlippert/archive/2011/05/26/atomicity-volatility-and-immutability-are-different-part-one.aspx啓動 –

回答

1

如果_progress是一個引用類型,那麼讀寫它的值(即改變引用)確實是一個原子操作,所以你不需要在你的例子中鎖定特定的代碼。但是如果你在setter或getter中改變了多個字段,或者如果這個字段不是帶有原子讀/寫的類型(例如double),那麼你需要鎖定。

如果你想幾個線程在任何時刻觀察同一個值,那麼你的確需要額外的鎖定。如果你不關心一個線程是否可以讀取一個值,另一個線程是另一個值(因爲它改變了中間值),那麼鎖定看起來並不是必須的。

我會讓揮發性肯定。 volatile就是爲了這個目的。它至少會阻止優化編譯器在不應該的時候緩存值(以防萬一它會這樣做)。

所以總結:爲了您的需要使用,您應該_progress波動,但你並不需要鎖定對它的訪問。

1

根據您的方法簽名,它看起來像只有一個線程將更新狀態。如果是這樣,那麼SetProgress上不需要任何鎖定。

如果你有多個線程更新進度變量,你仍然不會需要有在功能的鎖,因爲它是原子(假設它是一個引用變量,它看起來像)。

但是,如果你讀取的值,然後添加一個數字(例如取當前的進度,並增加10),那麼你將需要鎖定該呼叫。如果這是你的意圖,因爲你的調用對象不應該爲這個對象的完整性負責,我會建議創建一個處理更新的方法。

public Progress IncrProgress(int incr) 
    { 
     lock (_progressLock) 
     { 
      // Get the current progress 
      int current = _progress.GetPercentage(); 
      current += incr; 
      _progress = new Progress(current); 
     } 
     return _progress; 
    } 

至於你的其他問題,_progress應該被標記爲volatile,因爲它被另一個線程更新。

http://msdn.microsoft.com/en-us/library/x13ttww7(v=vs.71).aspx

+0

我認爲這個對象是不可變的。往上看。 – SACO

+0

基於此,您可能不需要任何鎖定,只需讀取中的易失性標誌。 –

0

我不認爲性能/場是隻讀的問題,如果沒有人是永遠不會改變對象的狀態。

所以,如果你只有一個線程設置進度和其他人閱讀它 - 不需要鎖定。

即使是這樣,使Progress對象不可變將是一個不錯的設計 - 因爲您明確地在設計中聲明:爲什麼您不需要任何鎖定?因爲該對象不能更改。另外,如果以後有更改/重新設計(決定更改/改變進度對象),則不會意外引入線程錯誤。

即使volatile關鍵字可能沒有必要?既然你不在乎你是否失去了一兩個進度。關鍵字全是關於「序列化訪問」的字段。

+0

我還是不太確定揮發性...... – SACO

+2

我會讓它揮發無疑。這正是爲了這個目的。它至少會阻止優化編譯器在不應該的時候緩存值(以防萬一它會這樣做)。 –