2013-03-04 46 views
0

我最近試圖寫一個鎖語句的例子。請考慮下面的代碼:爲什麼一開始就有線程競賽?

public partial class Form1 : Form 
    { 
     private class Concurrency 
     { 
      private int _myValue; 
      private object _locker = new object(); 
      public int Value 
      { 
       set 
       { 
        lock (_locker) 
        { 
         _myValue = value; 
         Thread.Sleep(new Random().Next(5, 25)); 
        } 
       } 

       get 
       { 
        return _myValue; 
       } 
      } 
     } 

     private Random _random; 
     private Concurrency _concurrency; 

     public Form1() 
     { 
      InitializeComponent(); 
      _random = new Random(); 
      _concurrency = new Concurrency(); 
     } 

     private void button1_Click(object sender, EventArgs e) 
     { 
      CreateTask(1); 
      CreateTask(2); 
     } 

     private void CreateTask(int taskId) 
     { 
      Task.Factory.StartNew(() => 
       { 
        for (int i = 0; i < 10; ++i) 
        { 
         int randomNumber = _random.Next(0, 50); 

         Console.WriteLine("Thread {0}, setting value {1}", taskId, randomNumber); 
         _concurrency.Value = randomNumber; 
         Console.WriteLine("Thread {0}, getting value {1}", taskId, _concurrency.Value); 

         Thread.Sleep(_random.Next(5, 15)); 
        } 
       }); 
     } 
    } 

結果是:

Thread 2, setting value 4 
Thread 1, setting value 22 
Thread 2, getting value 22 
Thread 1, getting value 22 
Thread 2, setting value 11 
Thread 2, getting value 11 
Thread 1, setting value 8 
Thread 2, setting value 41 
Thread 1, getting value 8 
Thread 1, setting value 30 
Thread 2, getting value 41 
Thread 1, getting value 30 
Thread 2, setting value 18 
Thread 1, setting value 42 
Thread 2, getting value 18 
Thread 2, setting value 30 
Thread 1, getting value 42 
Thread 1, setting value 24 
Thread 2, getting value 30 
Thread 1, getting value 24 
Thread 2, setting value 13 
Thread 1, setting value 7 
Thread 2, getting value 13 
Thread 2, setting value 13 
Thread 1, getting value 7 
Thread 2, getting value 13 
Thread 1, setting value 38 
Thread 2, setting value 19 
Thread 1, getting value 38 
Thread 1, setting value 4 
Thread 2, getting value 19 
Thread 2, setting value 44 
Thread 1, getting value 4 
Thread 2, getting value 44 
Thread 1, setting value 48 
Thread 2, setting value 12 
Thread 1, getting value 48 
Thread 1, setting value 47 
Thread 2, getting value 12 
Thread 1, getting value 47 

正如你所看到的,一切都很好,但事先設置/獲取情況:線程2套值2,但得到22而且它不是單一的情況下,它每次都發生。我知道設置和獲取不是原子的,應該圍繞任務中的指令設置鎖定,但爲什麼第一次嘗試總是失敗並且其他工作正常?

編輯:

我更新併發類這樣的:

private class Concurrency 
     { 
      private static Random _random = new Random(); 
      private int _myValue; 
      private object _locker = new object(); 
      public int Value 
      { 
       set 
       { 
        lock (_locker) 
        { 
         _myValue = value; 
         Thread.Sleep(_random.Next(5, 250)); 
        } 
       } 

       get 
       { 
        return _myValue; 
       } 
      } 
     } 

請注意,我還擴大了時間範圍的Thread.Sleep。結果是:

Thread 2, setting value 3 
Thread 1, setting value 9 
Thread 2, getting value 9 
Thread 2, setting value 44 
Thread 1, getting value 9 
Thread 1, setting value 35 
Thread 2, getting value 44 
Thread 2, setting value 32 
Thread 1, getting value 35 
Thread 1, setting value 25 
Thread 2, getting value 32 
Thread 2, setting value 15 
Thread 1, getting value 25 
Thread 1, setting value 5 
Thread 2, getting value 15 
Thread 2, setting value 34 
Thread 1, getting value 5 
Thread 1, setting value 42 
Thread 2, getting value 34 
Thread 2, setting value 36 
Thread 1, getting value 42 
Thread 1, setting value 8 
Thread 2, getting value 36 
Thread 2, setting value 42 
Thread 1, getting value 8 
Thread 1, setting value 16 
Thread 2, getting value 42 
Thread 2, setting value 0 
Thread 1, getting value 16 
Thread 1, setting value 43 
Thread 2, getting value 0 
Thread 2, setting value 20 
Thread 1, getting value 43 
Thread 1, setting value 30 
Thread 2, getting value 20 
Thread 2, setting value 38 
Thread 1, getting value 30 
Thread 1, setting value 0 
Thread 2, getting value 38 
Thread 1, getting value 0 

確實沒有什麼變化。我猜這不是Random問題,而是其他一些問題。

+0

嘗試拉出for循環(從任務內部到button1_Click例程)... – 2013-03-04 13:10:19

回答

-1

正如您指出的,鎖定不正確。所以這更多的是「爲什麼它似乎工作,除了一開始?」。 (我只是重申你的問題)

[編輯]

既然你改變了代碼刪除我在談論這個問題,這裏的另一個想法 - 我認爲這真的是答案。

你有你的代碼的方式,線程退出鎖定和讀取值之間有一段非常短的時間。

檢查你的二傳手:

set 
{ 
    lock (_locker) 
    { 
     _myValue = value; 
     Thread.Sleep(_random.Next(5, 25)); 
    } 
} 

現在,如果線程1是鎖內,它將設置_myValue,然後睡覺。在此期間的線程2將坐在等待進入鎖。

當線程1退出睡眠時,它立即離開鎖並用的代碼的下一行,在這種情況下是打印的電流值從線繼續:

Console.WriteLine("Thread {0}, getting value {1}", taskId, _concurrency.Value); 

除非線程1是其間取消調度退出鎖定並解除引用_concurrency.Value,它將收到正確的值。由於時間太短,在此期間不太可能不計劃。

如果線程1 被取消預定,那麼thread2將能夠輸入鎖並在thread1取消引用之前更改_myValue

做任何事情來增加線程設置和獲取值之間的時間將使得更可能觀察到「不正確」的值。

請嘗試下列程序,然後取消註釋// Try with this sleep uncommented.指示的行。你會看到更多的行打印「數字不匹配」。

using System; 
using System.Threading; 
using System.Threading.Tasks; 


namespace Demo 
{ 
    class Program 
    { 
     private static void Main(string[] args) 
     { 
      Console.WriteLine("Starting"); 
      CreateTask(1); 
      CreateTask(2); 
      Console.ReadKey(); 
     } 

     private static void CreateTask(int taskId) 
     { 
      Task.Factory.StartNew(() => 
      { 
       for (int i = 0; i < 10; ++i) 
       { 
        int randomNumber = _random.Next(0, 50); 

        Console.WriteLine("Thread {0}, setting value {1}", taskId, randomNumber); 
        _concurrency.Value = randomNumber; 
        // Thread.Sleep(10); // Try with this sleep uncommented. 
        int test = _concurrency.Value; 
        Console.WriteLine("Thread {0}, getting value {1}", taskId, test); 

        if (test != randomNumber) 
        { 
         Console.WriteLine("Number mismatch."); 
        } 

        Thread.Sleep(_random.Next(5, 15)); 
       } 
      }); 
     } 

     private static Random _random = new Random(); 
     private static Concurrency _concurrency = new Concurrency(); 

    } 

    class Concurrency 
    { 
     private int _myValue; 
     private object _locker = new object(); 
     public int Value 
     { 
      set 
      { 
       lock (_locker) 
       { 
        _myValue = value; 
        Thread.Sleep(_random.Next(5, 25)); 
       } 
      } 

      get 
      { 
       return _myValue; 
      } 
     } 

     static Random _random = new Random(); 
    } 
} 

那麼爲什麼一開始就會失敗呢?那麼,我認爲這只是系統啓動線程的一種人爲因素。

+0

這兩個線程之間共享隨機數,不是嗎? – Rawling 2013-03-04 12:54:50

+0

啊,我錯過了......我看到了Form1類中的共享'_random',並且錯過了他們創建_new_ randoms的位。 – Rawling 2013-03-04 12:58:51

+0

我更新了我的問題。 – 2013-03-04 13:09:03

1

它發生多次,而不僅僅是第一個

您「看到」它只是一次,而實際上錯誤在您的程序中。有可能每次你看到兩個「設置......」你可能會讀最後一個。想象一下這種情況:

 
Main Thread 1  Thread 2 
Value = 0   
int x1 = Value  
        Value = 2 
        int x2 = Value 
WriteLine(x1)  
        WriteLine(x2) 

輸出正確(線程1爲0,線程2爲2)。現在想象一下,如果安排是這樣的:

 
Main Thread 1  Thread 2 
Value = 0   
        Value = 2 
int x1 = Value  
WriteLine(x1)  
        int x2 = Value 
        WriteLine(x2) 

你會得到一個錯誤結果因爲兩個線程,你會讀出的數值2.其實這不是因爲鎖定的唯一操作是集,線程2的寫入操作(屬性值的設置)之前將不執行線程1的讀取操作(屬性值的獲取)。

最後再看看this post如果你這樣寫:

將會看到這樣的代碼可能會失敗(完全是出於同樣的原因)
++_concurrency.Value; 
+0

好吧,我已經知道,我知道鎖的位置是錯誤的。但是,爲什麼 - 無論我運行這個應用程序多少次 - 只有第一個設置/獲取失敗,其他都可以? – 2013-03-04 12:51:22

+0

隨機對象的「共享」種子可能有所貢獻(畢竟鎖定釋放和屬性獲得之間的時間間隔非常短),但重點是您不能依賴/研究/檢查指令執行順序不同的線程。至少沒有這麼高的水平。 – 2013-03-04 13:00:26

+0

也許(因爲我之前說過的原因,這只是一個無用的猜測),因爲第一次運行第二個線程時已經過去了(因爲在創建第二個任務時第一個任務還在運行)。 – 2013-03-04 13:03:26