2013-10-27 29 views
2

爲什麼,如果我有這樣的語句:併發線程可以同時檢查相同的對象鎖嗎?

private int sharedValue = 0; 

public void SomeMethodOne() 
{ 
    lock(this){sharedValue++;} 
} 

public void SomeMethodTwo() 
{ 
    lock(this){sharedValue--;} 
} 

因此,對於一個線程進入一個鎖如果另一個線程操作它,必須先檢查。如果不是,它可以進入並且必須將某些內容寫入內存,但這肯定不能是原子的,因爲它需要讀取和寫入。

那麼爲什麼一個線程不可能讀取鎖,而另一個線程正在將它的所有權寫入它呢?

簡化爲什麼兩個線程不能同時進入鎖定?

+1

查看另一個類似的問題在這裏:http://stackoverflow.com/questions/14758088/how-are-atomic-operations-implemented-at-a-hardware-level – blt

+1

他們是原子..具體來說,他們是一個原子更新的同步塊索引..這只是一個整數。 –

+1

這是鎖定的關鍵 - 以便您可以編寫安全的併發線程使用的代碼。 – Enigmativity

回答

2

它看起來像你基本上是問如何鎖工作。鎖怎樣才能以原子的方式保持內部狀態而不必建立鎖?它似乎是一個chicken and egg problem起初不是嗎?

這一切都是因爲compare-and-swap(CAS)的操作而發生的。 CAS操作是一個硬件級別的指令,它執行兩個重要的事情。

  • 它生成一個內存屏障,以便指令重新排序受到約束。
  • 它將內存地址的內容與另一個值進行比較,如果它們相等,則將原始值替換爲新值。它以原子的方式完成所有這些。

從最基本的層面來看,這就是技巧的完成。並非所有其他線程都在閱讀時被阻止,而另一線程正在寫入。這完全是錯誤的思考方式。實際發生的情況是所有線程都同時作爲編寫者。該策略比悲觀更樂觀。每個線程都試圖通過執行稱爲CAS的特殊類型的寫入來獲取鎖。您實際上可以通過Interlocked.CompareExchange(ICX)方法在.NET中訪問CAS操作。每一個同步原語都可以從這個單獨的操作中建立。

如果我要在C#中完全從頭開始編寫類似Monitor的類(即關鍵字lock在幕後使用的),我可以使用Interlocked.CompareExchange方法來完成。這是一個過度簡化的實現。請記住,這當然是而不是 .NET Framework如何做到這一點。 我提供下面的代碼的原因是向您展示如何在純粹的C#代碼中完成而不需要幕後的CLR魔術,以及它可能讓您考慮微軟如何實施它。

public class SimpleMonitor 
{ 
    private int m_LockState = 0; 

    public void Enter() 
    { 
     int iterations = 0; 
     while (!TryEnter()) 
     { 
      if (iterations < 10) Thread.SpinWait(4 << iterations); 
      else if (iterations % 20 == 0) Thread.Sleep(1); 
      else if (iterations % 5 == 0) Thread.Sleep(0); 
      else Thread.Yield(); 
      iterations++; 
     } 
    } 

    public void Exit() 
    { 
     if (!TryExit()) 
     { 
      throw new SynchronizationLockException(); 
     } 
    } 

    public bool TryEnter() 
    { 
     return Interlocked.CompareExchange(ref m_LockState, 1, 0) == 0; 
    } 

    public bool TryExit() 
    { 
     return Interlocked.CompareExchange(ref m_LockState, 0, 1) == 1; 
    } 
} 

該實現演示了一些重要的事情。

  • 它顯示瞭如何使用ICX操作來自動讀取和寫入鎖定狀態。
  • 它顯示了可能發生的等待情況。

注意我是如何使用Thread.SpinWaitThread.Sleep(0)Thread.Sleep(1)Thread.Yield而鎖等待被收購。等待策略過於簡化,但它的確已經接近real life algorithm implemented in the BCL。我故意在上面的Enter方法中保持代碼簡單,以便更容易發現關鍵位。這不是我通常會如此實施的方式,但我希望它能夠將重點放在首位。

另請注意,我的SimpleMonitor上面有很多問題。這只是少數。

  • 它不處理嵌套鎖定。
  • 它不提供WaitPulse方法像真正的Monitor類。他們真的很難做對。

的CLR將實際使用的存儲器的特殊塊上的每個引用類型存在。這塊內存被稱爲「同步塊」。 Monitor將操縱這塊內存中的位來獲取和釋放鎖。這個動作可能需要一個內核事件對象。你可以閱讀關於Joe Duffy's blog的更多信息。

+0

嗨。感謝您的迴應。我正在閱讀Duffy(Windows上的Concurrent Programming)一書,它討論了Dekker和Dijkstra的算法,以及你上面提到的內容。 – William

+0

@ user1743962:這是一本好書。請務必閱讀關於旋轉等待的部分,從頁碼開始。這是我在回答我的等待策略時所使用的另一個參考。 –

1

lock在C#中用於創建實際用於鎖定的對象Monitor

您可以在這裏閱讀更多關於Monitorhttp://msdn.microsoft.com/en-us/library/system.threading.monitor.aspx。所述MonitorEnter方法確保只有一個線程可以在時間進入臨界區:

獲取用於一個對象的鎖。這一行動也標誌着關鍵部分的開始。除非使用不同的鎖定對象執行關鍵部分中的指令,否則沒有其他線程可以進入關鍵部分。

順便說一句,你應該避免鎖定thislock(this))。您應該在類(靜態或非靜態)上使用私有變量來保護關鍵部分。你可以閱讀更多在上面提供的相同的鏈接,但其原因是:

當選擇在其上同步的對象,你應該只在私有或內部對象鎖定。鎖定外部對象可能會導致死鎖,因爲不相關的代碼可以選擇相同的對象來鎖定以達到不同的目的。

相關問題