2010-05-14 38 views
1

我正在使用Dictionary作爲後備存儲的線程安全集合。鎖定函數是否可用於實現線程安全枚舉?

在C#中,你可以做到以下幾點:

private IEnumerable<KeyValuePair<K, V>> Enumerate() { 
    if (_synchronize) { 
     lock (_locker) { 
      foreach (var entry in _dict) 
       yield return entry; 
     } 
    } else { 
     foreach (var entry in _dict) 
      yield return entry; 
    } 
    } 

我發現這樣做在F#的唯一方法是使用監控,如:

let enumerate() = 
     if synchronize then 
      seq { 
       System.Threading.Monitor.Enter(locker) 
       try for entry in dict -> entry 
       finally System.Threading.Monitor.Exit(locker) 
      } 
     else seq { for entry in dict -> entry } 

可以這樣用做鎖定功能?或者,有沒有更好的方法來做到這一點?我不認爲返回迭代集合的副本將工作,因爲我需要絕對同步。

+0

我同意@kvb,原來的代碼/設計充其量是可疑的。 – Brian 2010-05-14 16:47:15

回答

4

我不認爲你可以用鎖定功能做同樣的事情,因爲你會試圖從它內部屈服。話雖如此,這在任何一種語言中都看起來像是一種危險的方法,因爲這意味着鎖可以保持任意時間量(例如,如果一個線程調用Enumerate()但並未一直列舉產生的IEnumerable<_>,那麼鎖將繼續保持)。

它可能更有意義反轉邏輯,提供沿着線的iter方法:

let iter f = 
    if synchronize then 
    lock locker (fun() -> Seq.iter f dict) 
    else 
    Seq.iter f dict 

這帶來的迭代您的控制下背,確保序列被完全重複(假設f不會阻止,這在任何情況下似乎都是必要的假設),並且鎖定在此後立即釋放。

編輯

這裏是代碼的例子,可能永遠持有鎖。

let cached = enumerate() |> Seq.cache 
let firstFive = Seq.take 5 cached |> Seq.toList 

我們已經開始鎖定,以便開始枚舉前5項。然而,我們沒有繼續完成其餘的順序,所以鎖定不會被釋放(也許我們會根據用戶的反饋或其他方式來列舉其餘的方式,在這種情況下鎖定會最終被釋放) 。

在大多數情況下,正確編寫的代碼將確保它處理原始枚舉器,但是一般情況下無法保證。因此,你的序列表達式應該被設計爲健壯的,只能被部分枚舉。如果你打算要求你的調用者一次枚舉所有的集合,那麼迫使他們通過你的函數來應用到每個元素要比返回他們可以隨意列舉的序列要好。

+0

很棒的建議。謝謝。我很好奇一個線程如何不完全枚舉集合可以保持鎖定,除非你的意思是在for循環中調用Thread.Sleep(很長一段時間)。否則,它看起來會跳出循環,在枚舉器上調用Dispose並釋放鎖。 – Daniel 2010-05-14 18:12:47

+0

@Daniel - 我已經添加了一個不釋放鎖的示例。如果調用代碼始終使用for循環,則可能安全,但還有其他方法可以使用IEnumerables。 – kvb 2010-05-14 19:05:05

+0

感謝您的補充說明。 – Daniel 2010-05-14 19:13:16

4

我同意kvb的代碼是可疑的,你可能不希望來保持鎖定。但是,有一種方法可以使用use關鍵字以更舒適的方式編寫鎖定。值得一提的是,它可能在其他情況下有用。

您可以編寫開始持有鎖,並返回IDisposable功能,從而釋放當它被設置在鎖:

let makeLock locker = 
    System.Threading.Monitor.Enter(locker) 
    { new System.IDisposable with 
     member x.Dispose() = 
     System.Threading.Monitor.Exit(locker) } 

然後你就可以,例如寫:

let enumerate() = seq { 
    if synchronize then  
    use l0 = makeLock locker 
    for entry in dict do 
     yield entry  
    else 
    for entry in dict do 
     yield entry } 

這是基本上使用use關鍵字來實現C#,如lock,它具有相似的屬性(允許您在離開作用域時執行某些操作)。所以,這更接近原始的C#版本的代碼。