2014-11-03 79 views
4

我有多個線程將數據寫入一個共同的源,並且我希望兩個線程互相阻塞,當且僅當他們觸及同一條數據時。在C#中命名鎖集合?

這將是不錯的辦法上的任意鍵鎖明確:

string id = GetNextId(); 
AquireLock(id); 
try 
{ 
    DoDangerousThing(); 
} 
finally 
{ 
    ReleaseLock(id); 
} 

如果沒有其他人試圖鎖定相同的密鑰,我希望他們能夠同時運行。

我可以通過一個簡單的互斥字典來實現這一點,但我需要擔心驅逐舊的未使用的鎖,如果該集變得太大,可能會成爲問題。

是否存在這種類型的鎖定模式的現有實現。

+0

http://msdn.microsoft.com/en-us/library/c5kehkcz.aspx – Rhumborl 2014-11-03 22:30:50

回答

7

您可以嘗試使用ConcurrentDictionary<string, object>來創建命名對象實例。當你需要一個新的鎖定實例(你以前沒有用過)時,你可以將它添加到字典中(通過GetOrAdd添加是一個原子操作),然後所有線程都可以共享同一個命名對象, ,根據你的數據。

例如:

// Create a global lock map for your lock instances. 
public static ConcurrentDictionary<string, object> GlobalLockMap = 
    new ConcurrentDictionary<string, object>(); 

// ... 

var oLockInstance = GlobalLockMap.GetOrAdd ("lock name", x => new object()); 

if (oLockInstance == null) 
{ 
    // handle error 
} 

lock (oLockInstance) 
{ 
    // do work 
} 
+2

這基本上是我要寫的答案,但我不認爲有必要驗證oLockInstance不爲null。 GetOrAdd永遠不應該返回null,因爲你總是從你提供的lambda中返回一個有效的對象。 – NSFW 2014-11-03 22:42:38

+0

@NSFW我同意你的看法,不應該 - 我只是把這一點作爲最後一道防線。這些年來我見過很多奇怪的事情。 :) – xxbbcc 2014-11-03 22:43:30

+0

好,簡單。你能想出一個確保GlobalMapLock的大小可以控制的好方法嗎?我可以有很多鑰匙。 – captncraig 2014-11-03 22:47:47

4

lock關鍵字(MSDN)已經這樣做了。

當你鎖定,您傳遞對象對鎖定

lock (myLockObject) 
{ 
} 

它使用Monitor類與特定對象進行同步同一個對象上使用lock任何線程。

由於字符串字面量「拘留」 –即,它們被緩存重用讓每一個字面相同的值實際上是在同一個對象–你也可以做這樣的字符串:

lock ("TestString") 
{ 
} 

由於不是處理字符串文字,您可以在C#: Strings with same contents中描述您實際讀取的字符串。

如果所使用的引用是從實際字符串(直接或間接)複製(直接或間接),它甚至可以工作。但我不會推薦它。這是非常脆弱的,並且可能會導致難以調試的問題,因爲可以創建具有與interned字符串相同值的字符串的新實例。

只有在別的東西進入同一個對象上的鎖定部分時鎖纔會被鎖定。因此,不需要保留字典,只需要適用的鎖定對象。

然而,實際上,您需要維護一個ConcurrentDictionary或類似的設備,以允許您的對象訪問相應的鎖定對象。

+1

也許我的問題是誤導。鎖鍵不是字符串文字,而是來自數據本身。 – captncraig 2014-11-03 22:32:28

+0

@captncraig然後,您需要創建表示該數據的對象並跟蹤它們。鑑於字符串的實現,你*可能會放棄只是鎖定字符串本身,但我不會指望它。 – BradleyDotNET 2014-11-03 22:34:00

+1

@captncraig檢查一下,除非你實習琴絃,否則它絕對不會奏效。看到我鏈接的帖子。 – BradleyDotNET 2014-11-03 22:37:19

2

可以使用ConcurrentDictionary<string, object>能夠創建和重用不同的鎖。如果您想要從字典中刪除鎖定,並且將來也可以重新打開相同的命名資源,那麼如果以前獲取的鎖定已被其他線程刪除或更改,則必須始終檢查關鍵區域內部。在離開臨界區之前,注意從字典中刪除鎖作爲末尾步驟。

static ConcurrentDictionary<string, object> _lockDict = 
     new ConcurrentDictionary<string, object>(); 

    // VERSION 1: single-shot method 

    public void UseAndCloseSpecificResource(string resourceId) 
    { 
     bool isSameLock; 
     object lockObj, lockObjCheck; 
     do 
     { 
      lock (lockObj = _lockDict.GetOrAdd(resourceId, new object())) 
      { 
       if (isSameLock = (_lockDict.TryGetValue(resourceId, out lockObjCheck) && 
            object.ReferenceEquals(lockObj, lockObjCheck))) 
       { 
        // ... open, use, and close resource identified by resourceId ... 

        // This must be the LAST statement 
        _lockDict.TryRemove(resourceId, out lockObjCheck); 
       } 
      } 
     } 
     while (!isSameLock); 
    } 

    // VERSION 2: separated "use" and "close" methods 
    //   (can coexist with version 1) 

    public void UseSpecificResource(string resourceId) 
    { 
     bool isSameLock; 
     object lockObj, lockObjCheck; 
     do 
     { 
      lock (lockObj = _lockDict.GetOrAdd(resourceId, new object())) 
      { 
       if (isSameLock = (_lockDict.TryGetValue(resourceId, out lockObjCheck) && 
            object.ReferenceEquals(lockObj, lockObjCheck))) 
       { 
        // ... open and use (or reuse) resource identified by resourceId ... 
       } 
      } 
     } 
     while (!isSameLock); 
    } 

    public bool TryCloseSpecificResource(string resourceId) 
    { 
     bool result = false; 
     object lockObj, lockObjCheck; 
     if (_lockDict.TryGetValue(resourceId, out lockObj)) 
     { 
      lock (lockObj) 
      { 
       if (_lockDict.TryGetValue(resourceId, out lockObjCheck) && 
        object.ReferenceEquals(lockObj, lockObjCheck)) 
       { 
        result = true; 
        // ... close resource identified by resourceId ... 

        // This must be the LAST statement 
        _lockDict.TryRemove(resourceId, out lockObjCheck); 
       } 
      } 
     } 
     return result; 
    }