2012-08-02 52 views
2

我有以下代碼:C#多線程和重新排序 - 這裏發生了什麼?

public static System.Int32 i = 0; 

    static void Main(string[] args) 
    { 
     new Thread(Worker1) { IsBackground = true }.Start(); 
     new Thread(Worker2) { IsBackground = true }.Start(); 

     Console.WriteLine("Running... Press enter to quit"); 
     Console.ReadLine(); 
    } 

    static void Worker1(object _) 
    { 
     while (true) 
     { 
      var oldValue = i; 
      i++; 
      var newValue = i; 

      if (newValue < oldValue) 
       Console.WriteLine("{2}, i++ went backwards! oldValue={0}, newValue={1}!", oldValue, newValue, DateTime.Now.ToString("HH:MM:ss.fff")); 
     } 
    } 

    static void Worker2(object _) 
    { 
     while (true) 
     { 
      i++; 
     } 
    } 

運行時,這會產生輸出看起來像這樣:

17:08:17.020, i++ went backwards! oldValue=6124653, newValue=6113984! 
17:08:17.057, i++ went backwards! oldValue=18764535, newValue=18752368! 
17:08:17.086, i++ went backwards! oldValue=27236177, newValue=27236176! 
17:08:17.087, i++ went backwards! oldValue=27550457, newValue=27535008! 
17:08:17.130, i++ went backwards! oldValue=40251349, newValue=40235492! 
17:08:17.137, i++ went backwards! oldValue=42339974, newValue=42323786! 
17:08:17.142, i++ went backwards! oldValue=43828602, newValue=43828436! 
17:08:17.149, i++ went backwards! oldValue=45969702, newValue=45959111! 
17:08:17.158, i++ went backwards! oldValue=48705847, newValue=48705549! 
17:08:17.230, i++ went backwards! oldValue=71199684, newValue=71199674! 

注:這是一個四核i7超線程在Windows 7中運行

至於我可以告訴大家,無論是:

  • 線程2在我讀取oldValue和newValue之間遞增約40億次(但不完全!)。但是,考慮到上面的數字和時間安排,似乎我在接下來的2秒內有10次獲得彩票的機會更高。

  • CPU和編譯器正在做一些重新排序...這似乎是合乎邏輯的解釋,但我不能完全弄清楚可能會導致這種情況的一系列操作?

任何人都可以看到它嗎?


爲了澄清問題:我故意尋找代碼來重現內存重新排序錯誤作爲教育練習的一部分。有很多方法可以解決這個問題,我主要對分析發生的事情感興趣。

@Tymek在下面顯示了(明顯是顯而易見的,現在指出)。

+0

它只是可能發生,操作系統需要一些資源,而執行您的程序,這導致了一種重新排序的感覺。只要期望通過多線程和多個非同步操作,每個可能的排列都會發生 - 遲早會發生。 – weismat 2012-08-02 06:23:05

回答

4

你肯定知道我++是不是原子,看起來真的很喜歡這樣的事情:

  • i從內存到寄存器,
  • 增量寄存器,
  • 寫寄存器到內存中i是,
  • return i;

該順序可以在任何階段中斷。

有考慮到這一點,從而產生厄運的消息,一種情況是如下:

  • 讓我們i100;
  • 線程2讀取100並被中斷;
  • 線程1執行了一段時間,使i遞增到120(幾個全循環),然後讀取i120,讀取i和它遞增到121var newValue = i;之前被中斷;
  • 線程2遞增i101並中斷;
  • 線程運行並執行var newValue = i < - 將讀取101;

現在oldValue120newValue101

1

如果你想分配舊值,我的增量和新值的分配發生「在同一時間」你應該使用一個鎖。 只需引入一個私有變量,然後使用此鎖。這看起來是這樣的:

lock(lockObj) 
{ 
    var oldValue = i;    
    i++;    
    var newValue = i; 
} 
+1

您將不得不鎖定所提供的代碼中的兩個i ++語句。只鎖定一個不會有太大的改變。 – mayu 2012-08-02 06:45:29

1

爲了避免這個問題,你可以使用Interlocked.Increment將garantee操作不會是原子和線程安全的。當你使用i ++時,它不是典型的,它的值可以在遞增期間被改變(來自另一個線程,例如)。

相關問題