2012-02-15 49 views
0

我與後端系統進行接口連接,在這種系統中,我永遠不會有與給定對象(由其數字ID標識)的多個開放連接,但不同的消費者可能會打開和關閉它們彼此獨立。在工廠方法中鎖定

粗略地說,我有一個工廠類片段是這樣的:

private Dictionary<ulong, IFoo> _openItems = new Dictionary<ulong, IFoo>(); 
private object _locker = new object(); 

public IFoo Open(ulong id) 
{ 
    lock (_locker) 
    { 
     if (!_openItems.ContainsKey(id)) 
     { 
      _openItems[id] = _nativeResource.Open(id); 
     } 

     _openItems[id].RefCount++; 

     return _openItems[id]; 
    } 
} 

public void Close(ulong id) 
{ 
    lock (_locker) 
    { 
     if (_openItems.ContainsKey(id)) 
     { 
      _openItems[id].RefCount--; 
      if (_openItems[id].RefCount == 0) 
      { 
       _nativeResource.Close(id); 
       _openItems.Remove(id); 
      } 
     } 
    } 
} 

現在,這裏的問題。在我的情況下,_nativeResource.Open是非常慢。這裏的鎖定是非常天真的,當有很多不同的併發.Open調用時,即使它們(很可能)引用不同的id並且不重疊,可能會很慢,特別是如果它們不在_openItems中緩存。

如何構建鎖定,以便我只阻止併發訪問到特定ID而不是所有呼叫者?

+0

您想要鎖定不在全局對象_locker上的ID。嘗試找到與單個ID相對應的對象。可能建立你自己的一套對象。 – mathk 2012-02-15 19:08:12

回答

3

你可能想要看的是一個條紋鎖定策略。這個想法是,你共享M個物品的N個鎖(在你的情況下是可能的ID),並選擇一個鎖,這樣對於任何ID,選擇的鎖總是一樣的。選擇鎖定此技術的典型方法是模事業部只是用N分男,取餘數,並與該索引使用鎖:

// Assuming the allLocks class member is defined as follows: 
private static AutoResetEvent[] allLocks = new AutoResetEvent[10]; 


// And initialized thus (in a static constructor): 
for (int i = 0; i < 10; i++) { 
    allLocks[i] = new AutoResetEvent(true); 
} 


// Your method becomes 
var lockIndex = id % allLocks.Length; 
var lockToUse = allLocks[lockIndex]; 

// Wait for the lock to become free 
lockToUse.WaitOne(); 
try { 
    // At this point we have taken the lock 

    // Do the work 
} finally { 
    lockToUse.Set(); 
} 
+0

不應該是lockToUse.Reset()而不是.WaitOne()? – Joe 2012-02-15 18:47:28

+0

編號AutoResetEvent上的'WaitOne'會阻塞,直到事件被設置(有人調用'Set',或者這是第一次等待它,它是通過傳給構造函數的'true'創建的)。只要事件被設置,第一個調用WaitOne的線程就會繼續,並且(原子地)事件被重置並阻塞所有其他等待線程。 – 2012-02-15 18:51:01

+0

啊,我明白了。我錯過了你默認的所有地方(在構造函數中)。謝謝。去試試看。 – Joe 2012-02-15 18:54:38

1

如果你是在.NET 4中,你可以試試ConcurrentDictionary與這些方針的東西:

private ConcurrentDictionary<ulong, IFoo> openItems = new ConcurrentDictionary<ulong, IFoo>(); 
private object locker = new object(); 

public IFoo Open(ulong id) 
{ 
    var foo = this.openItems.GetOrAdd(id, x => nativeResource.Open(x)); 

    lock (this.locker) 
    { 
     foo.RefCount++; 
    } 

    return foo; 
} 

public void Close(ulong id) 
{ 
    IFoo foo = null; 

    if (this.openItems.TryGetValue(id, out foo)) 
    { 
     lock (this.locker) 
     { 
      foo.RefCount--; 

      if (foo.RefCount == 0) 
      { 
       if (this.openItems.TryRemove(id, out foo)) 
       { 
        this.nativeResource.Close(id); 
       } 
      } 
     } 
    } 
} 

如果任何人都可以看到任何明顯的問題,請讓我知道!

+0

ConcurrentDictionary.GetOrAdd中的工廠方法是* not *線程安全的。 nativeResource.Open *可以*使用此實現執行*多次*。 [Check this](http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2535774-additional-overload-to-system-concurrent-concurren) – Anastasiosyal 2012-02-20 11:22:44