2013-08-23 92 views
5

我可能在這裏錯過了一些基本的東西,但仍然會感謝您的理解幫助。所以,我有以下簡單的多線程程序我寫道:簡單的C#併發/多線程

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading; 
using System.Threading.Tasks; 

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      //   List<int> outcome = new List<int>(); 
      Test t = new Test(); 

       Thread thread1 = new Thread(new ThreadStart(t.call1)); 
       Thread thread2 = new Thread(new ThreadStart(t.call2)); 
       thread1.Start(); 
       thread2.Start(); 
       Thread.Sleep(3000); //Give enough time for threads to end 
       Console.Write("{0},", t.mSum); 
       t.mSum = 0; 
     } 
    } 

    class Test 
    { 
     public int mSum = 0; 
     public void call1() 
     { 
      //lock (this) 
      //{ 

      for (int i = 0; i < 100; i++) 
      { 
       Console.WriteLine("Hello Thread 1, mSum value: {0}", mSum); 
       mSum = mSum + 1; 
       Console.WriteLine("Goodbye Thread 1, mSum value: {0}", mSum); 
      } 
      //} 
      // Console.WriteLine(mSum); 
     } 
     public void call2() 
     { 
      for (int i = 0; i < 100 ; i++) 
      { 
       Console.WriteLine("Hello Thread 2, mSum value: {0}",mSum); 
       mSum = mSum + 1; 
       Console.WriteLine("Goodbye Thread 2, mSum value: {0}",mSum); 
      } 
     } 
    } 
}  

所以我希望這個輸出是nondetermenistic因爲可以隨時發生正確的上下文切換?但是,當我運行程序時,我得到下面的輸出(只輸出的一部分,畸形是由於我可憐stackoverflow.com問題張貼技能):

 
Hello Thread 1, mSum value: 62 Goodbye Thread 1, mSum value: 63 
Hello Thread 1, mSum value: 63 Goodbye Thread 1, mSum value: 64 
Hello Thread 2, mSum value: 59 Goodbye Thread 2, mSum value: 65 
Hello Thread 2, mSum value: 65 Goodbye Thread 2, mSum value: 66 

因此,假設我寫了這個權利,並MSUM確實在線程之間共享(看起來像是......) - 我怎樣才能解釋線路號碼? 3?線程2讀取59,加1,然後我們得到65!

我發現了一種新的數學嗎? :)

+0

這是按照正確的順序,沒有重組?該代碼顯示,「Hello」行和「Goodbye」行之間應該有一個換行符,如果你刪除了它,那很好,我只是試着去確定它已經到了「Hello 1,再見1 ... 2你好,再見2" 如果你知道我的「米說 –

+0

是順序是正確的:左到右,然後由底部(62-> 63,63-> 64,59-> 65, 65-> 66)。我試圖在這裏添加一個圖像,但不能。 – Tal

回答

9

您未鎖定共享變量mSummSum = mSum + 1不是原子操作。很明顯,打印到控制檯,增加一個變量然後再次打印到控制檯並不是原子更多:)線程可能存在許多可能的交錯方式。例如:

1)MSUM = 0 [線程1正在工作]

2)MSUM = 1 [線程1正在工作]

3)MSUM = 2 [線程2是工作]

4)...

5)MSUM = 59 [線程2是工作]和它得到後搶先 「你好...」

6)MSUM = 60 [THRE AD1正在]

7)MSUM = 61 [線程1正在工作]

8)...

9)MSUM = 64 [線程2是工作]喚醒遞增線線程2繼續之前,和計算65

5)Thread2甚至可以在mSum = mSum + 1從內存中讀取MSUM之後,但計算mSum + 1之前已經捷足先登。

3

因爲您正在使用多個線程,所以在第一個和第二個調用之間該值可能已發生變化。

如果您想確保報告添加將使用的正確值,則必須使用鎖定。

0

正如您所指出的,mSum被多個線程之間共享,分辨率會是個鎖,或將mSumvolatile整數

http://msdn.microsoft.com/en-us/library/x13ttww7.aspx

所以,你可以改變這一點:

public int mSum = 0; 

要:

public volatile int mSum = 0; 

但正如其他人指出的,在更復雜的情況下,您可能想要使用鎖。

而且,也沒有必要來包裝你的電話在ThreadStart一個新的實例,所以不是這樣的:

new Thread(new ThreadStart(t.call1)); 

你可以這樣做:

new Thread(t.call1); 
+0

嗨,我知道這不是線程安全的,我知道我應該用鎖來解決這個問題,但我只是想了解.NET如何通過這個代碼在內部工作,也就是說,mSum是共享的,但是我試圖理解邏輯。 – Tal

+0

它並不是真正的共享,它只是從多個線程中訪問,正如Sean所說,mSum的價值可能在WriteLine和添加之間改變 –

0

的MSUM從改變在第一個WriteLine和第二個WriteLine mSum之間同時有2個線程A和B可能會改變。 mSum沒有鎖定或不穩定,所以你在內存中沒有任何障礙,所以你可能成爲一個非常奇怪的結果,但它取決於你的CPU類型和現金。

只是把這意味着MSUM將在CPU中未兌現之前MSUM揮發性字。再次運行應用程序並查看控制檯!?

+0

似乎不管我運行多少次該程序,mSum的最終結果始終爲200沒有鎖。我做了很多次。 – Tal

+1

@Tal正因爲競爭條件的存在並不意味着它們每一次都會發生。同時的想法是,他們可能或可能不會發生。不同的競賽條件也有不同的發生概率,可能受到其他行爲的影響。例如,您可以在具有更多/更少內核或更多/更少一般功率的另一臺計算機上運行相同的程序,並獲得不同的結果。如果你的程序中斷了.01%的時間是你能接受的?也許在這種情況下。如果你的銀行錯過了支票存款的0.01%,你會關心嗎? – Servy

+0

@Tal importent要知道你不能在同一時間點讀兩次相同的內存位。它的堅果和膽量雖然非常複雜,但是現代多核CPU對這些類型的東西都有各種各樣的緩存。 –