2015-01-06 34 views
1

我得到了book about programming in C#.有一個例子。讓我複製並粘貼上下文。比較和交換作爲非原子操作

...介紹了使用聯鎖(這裏省略)。

您還可以使用比較交換方法。該方法首先檢查預期值是否存在;如果是,則用另一個值替換它。

清單1-41顯示比較和交換非原子操作中的值時可能出現的問題。

LISTING 1-41和比較交換作爲非原子操作

class Program 
{ 
    static int value = 1; 
    static void Main(string[] args) 
    { 
     Task t1 = Task.Run(() => 
      { 
       if(value==1) 
       { 
        // Removing the following line will change the output 
        Thread.Sleep(1000); 
        value = 2; 
       } 
      }); 
     Task t2 = Task.Run(() => 
     { 
      value = 3; 
     }); 
     Task.WaitAll(t1, t2); // Displays 2 
     Console.WriteLine(value); 
    } 
} 

任務T1開始運行並看到等於。與此同時,t2將值更改爲然後t1將其更改回。爲了避免這種情況,你可以使用下面的聯鎖聲明:

Interlocked.CompareExchange(ref value, newValue, compareTo); 

這可以確保在比較的價值和交換它的一個新的是一個原子操作。這樣,沒有其他線程可以在比較和交換它之間改變它的值。

這本書不提供更多的細節,只是在這一點結束本節。

我的問題是如何申請Interlocked.CompareExchange(ref value, newValue, compareTo);的代碼?

+1

我不知道該把它放在哪裏。 –

+3

假設將't1'內的所有代碼替換爲'Interlocked.CompareExchange(ref value,2,1);' –

+1

'Interlocked.CompareExchange(ref value,2,1)''替換't1'。 – Jodrell

回答

4

嗯,我猜他們的意思是,以取代非原子塊:

if(value==1) 
{ 
    // Removing the following line will change the output 
    Thread.Sleep(1000); 
    value = 2; 
} 

隨着

Interlocked.CompareExchange(ref value, 2, 1); 

編輯,並Interlocked.CompareExchange解決什麼問題?

兩個任務t1和t2都可能同時更改value

這裏此代碼序列在多線程代碼共同的反模式 - 所述ABA problem

if(someUnsynchronizedCondition) 
{ 
    ... Other random code here 
    doSomeActionDependentOnTheCheckedCondition(); 
} 

這樣做的問題是,這是檢查(if (value==1))的條件不一定是真實的任何再在(在你的情況下爲value = 2;),因爲其他線程可以在該線程從if檢查移動到依賴操作所需的同一時間更改該變量(顯然,「其他隨機代碼「需要2秒睡眠,風險就越大)。但即使沒有額外的代碼(即原始代碼仍然傾向於ABA,即使完全除去了ABA)也總是存在風險。

在這本書中的示例中,動作是突變的條件下使用相同的變量,所以因此與Interlocked.CompareExchange這樣的便捷的方式 - 針對英特爾處理器,這相當於一個lock cmpxchg [reg1], reg2atomic instruction

從更一般的意義上說(即依賴操作不僅僅是改變檢查變量),您需要使用其他的同步方式,例如lockReaderWriterLockSlim,特別是如果存在偏差朝着更多的讀者而不是作家。

回覆:最終結果

是,代碼將現在一直顯示3,僅僅是因爲如果第一Task T1執行第一,它將值更改爲2,其將由第二Task T2否決將其設置爲3,但是,如果所述第二任務運行第一(設置爲3),然後將T1不是因爲它是3,而不是1

+0

然後它顯示3,它是正確的嗎?我試過你的代碼。 –

+0

@HaiZhao:是的,它應該顯示3,因爲't1'在't2'之前開始。 –

+1

是的,結果總是3 - 我已經解釋了一些。 – StuartLC

0
class Program 
{ 
    static int value = 1; 
    static void Main(string[] args) 
    { 
     Task t1 = Task.Run(() => 
     { 
      Interlocked.CompareExchange(ref value, 2, 1); 
     }); 
     Task t2 = Task.Run(() => 
     { 
      value = 3; 
     }); 
     Task.WaitAll(t1, t2); 
     Console.WriteLine(value); 
    } 
} 
+0

可能要更新評論。當然它現在會顯示3。 –

+0

公平點。我剛剛複製了上下文的原始代碼,並替換了必須替換的內容。 – PiotrWolkowski

2

它取代了單獨的比較和交換指令更新值。在從T1塊:

if(value==1) //Compare 
{ 
    Thread.Sleep(1000); 
    value = 2; //Exchange 
} 

希望你會看到,而T1是睡着了,T2可以削減和值設置爲3,因爲你的比較和交換是分開的 - 即不是原子。

如果更換整塊具有:

Interlocked.CompareExchange(ref value, 2, 1); 

然後運行保證了比較和交換都一起做,並沒有其他的線程可以在這些操作的中間切開。

這不是一個很好的例子,因爲你也一定會失去Thread.sleep,這使得它不太直觀地遵循恕我直導。

3

Interlocked.CompareExchange(ref value, newValue, compareTo);基本上沒有這樣的:

if(value==compareTo) 
{ 
    value = newValue; 
} 

但是在一個原子,換言之,非中斷的方式。這可以防止您在示例代碼中檢查該值時遇到的問題,但是在爲其分配新值之前,該值會發生更改。在共享變量的多線程代碼中,這可能非常重要。

你可能會看到這種類型的其他技術。例如,使用對象lock

lock(someObject) 
{ 
    if(value==compareTo) 
    { 
     value = newValue; 
    } 
} 

但這隻會工作,如果每隔試圖改變同一對象上value鎖。