2016-01-04 64 views
1

在一個項目即時通訊使用線程。我試圖做一個安全的線程,dosn't'腐敗'的數據。我的線程在後臺運行,並在另一個類上調用函數,我可以調用Go和Go2,一個函數添加,一個從列表中刪除。我不希望他們在同一時間運行,就是下面的情況之間的差異:鎖定傳遞對象發生了什麼?

static readonly object _locker1 = new object(); 
static readonly object _locker2 = new object(); 


public void Go(Object something) 
{ 
    lock (_locker1) 
    { 
    myList.add(something); 
    } 
} 

public void Go2(Object something) 
{ 
    lock (_locker2) 
    { 
    myList.Remove(something); 
    } 
} 

如果我將其替換GO 2:

public void Go2(Object something) 
{ 
    lock (_locker1) 
    { 
    myList.Remove(something); 
    } 
} 

注鎖定參數。

第三種情況會幫助我理解,可以說我從另一個線程(線程2)調用Go,它可以運行,因爲_locker1被thread2鎖定,而Go2(其中_locker 1被thread2鎖定)是從thread1調用?

static readonly object _locker1 = new object(); 
static readonly object _locker2 = new object(); 


public void Go(Object something) 
{ 
    lock (_locker1) 
    { 
    //Can I call Go2 which is locked by the same object? 
    Go2(something); 
    } 
} 

public void Go2(Object something) 
{ 
    lock (_locker1) 
    { 
    myList.Remove(something); 
    } 
} 

有人可以解釋傳遞給鎖的值是什麼嗎?

回答

5

這很簡單:如果兩個鎖使用相同的對象,它們將不會同時運行。在你的第一個片段中,由於Go和Go2鎖定不同的對象,它們可以同時運行並做不好的事情。

+0

@cecilloPardo可以說我打電話Go和後來的Go2(而Go沒有完成)它只是等Go完成或跳過Go2,因爲它被鎖定了? –

+0

在第一種情況下,Go2 Go2會同時運行。在第二種情況下,執行將停止在Go2的鎖定語句中,直到Go1的鎖定語句結束。 –

+0

@cecilo謝謝!如果我在情景2中打電話給Go2,我是否會造成死鎖? –

2

作爲documentation說:

鎖關鍵字標誌着一個語句塊作爲通過獲得互斥鎖給定對象,執行語句,然後解除鎖定的一個關鍵部分。從而

lock監視器給定對象語法糖。在使用第一個代碼示例時,如果兩個線程都執行Go(或Go2),則它們必須等待對方,而如果這兩個線程執行不同的方法,則會導致同步衝突。

在第二個代碼示例中,由於您始終鎖定解鎖同一對象,因此可以保證不會同時執行myList.addmyList.remove

編輯:在第三種情況下,recursive locks are counted。這意味着如果第一個線程調用Go並且第二個線程調用Go2,如果第一個線程首先進入lock,它將會調用Go2獲得對鎖的訪問權,移除該項,從遞歸調用返回,離開lock,只有第二個線程才能進入Go2的鎖定。如果第二個線程贏得比賽,第二個線程將首先進入lock並阻止來自外部lock的第一個線程。只有當第二個線程離開lockGo2時,第一個線程纔可以輸入Golock(並執行遞歸調用)。

+0

第三種情況怎麼樣,如果從不同的線程調用Go1,Go可以去叫Go2嗎? –

+0

@SvenB:查看更新的答案。 –

0

lock語句實際上轉化爲引擎蓋下的監視器鎖定,如:

bool lockWasTaken = false; 
var temp = obj; 
try 
{ 
    Monitor.Enter(temp, ref lockWasTaken); 
    // body 
} 
finally 
{ 
    if (lockWasTaken) 
    { 
     Monitor.Exit(temp); 
    } 
} 

要了解更多有關監控鎖是如何工作的,請看這裏:MSDN Documentation

要詢價MSDN:

使用Enter獲取Monitoron作爲參數傳遞的對象。如果另一個線程在對象上執行了Enter但尚未執行相應的Exit,則當前線程將阻塞,直到另一個線程釋放該對象。同一個線程在不阻塞的情況下多次調用Enter是合法的;然而,在等待對象的其他線程將被解除阻塞之前,必須調用相同數量的Exit調用。 使用Monitor來鎖定對象(即引用類型),而不是值類型。當您將值類型變量傳遞給Enter時,它將作爲對象裝箱。如果您再次將相同的變量傳遞給Enter,則會將其作爲單獨的對象裝箱,並且線程不會阻止。在這種情況下,Monitor應該保護的代碼不受保護。此外,當您將該變量傳遞給Exit時,還會創建另一個單獨的對象。因爲傳遞給Exit的對象與傳遞給Enter的對象不同,所以Monitor引發SynchronizationLockException。有關更多信息,請參閱概念主題監視器。

這意味着你的代碼將被阻塞在2個不同的對象上,你只想阻塞其中的一個以使它安全。

1

傳遞給鎖的值是共享狀態的象徵,將在塊(花括號)中訪問鎖定語句。每個對該值的鎖定請求將按順序排隊並處理。一次只允許一個該值的請求者處理,所以共享狀態一次只能由一個請求者訪問。

抽象

這就像如果我有一個橡膠雞開會,我說「只有 人拿着橡皮雞能說話」。在這個 的情況下,橡皮雞是鎖定參數,並且 說話的能力是共享資源。希望發言的每個人都將形成 系列。只有一個人可以拿着雞,所以只有一個人可以說話 。當說話的人完成後,他們把雞肉遞給 下一個人。

在你的第一種情況下,你有兩個橡膠雞:locker1(橡皮雞1)和locker2(橡皮雞2)。因此,Go和Go2不會彼此等待一個回合(他們都有一隻雞!)。調用Go的線程可以添加到myList,而調用Go2的另一個線程可以同時訪問myList以從列表中刪除項目。然而,兩個調用Go的線程將等待輪到它們,因爲它們都需要相同的橡皮雞:locker1;兩個線程調用Go2也是如此。

如果你讓Go和Go2使用相同的值(同一只雞),那麼他們將不得不等待輪到獲得該值的鎖。這將阻止他們一個線程調用Go,另一個線程調用Go2同時訪問myList。

相關問題