2010-05-14 4 views
1

사전을 백업 저장소로 사용하는 스레드로부터 안전한 컬렉션에서 작업하고 있습니다. 다음 작업을 수행 할 수있는 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

답변

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 

처음 다섯 항목을 통해 열거하기 시작했습니다. 그러나 우리는 나머지 시퀀스를 계속하지 않았으므로 잠금이 해제되지 않습니다 (나중에 사용자 의견이나 다른 것을 기반으로 나머지를 열거 할 것입니다.이 경우 잠금이 결국 해제됩니다) .

대부분의 경우 올바르게 작성된 코드는 원래 열거자를 처분하도록 보장하지만 일반적으로이를 보장 할 방법이 없습니다. 따라서 시퀀스 표현식은 열거 형 부분 만 견딜 수 있도록 설계되어야합니다. 호출자가 컬렉션을 모두 한 번에 열거하도록 요구하려면 각 요소에 적용 할 함수를 전달하도록 강요하면 원하는대로 열거 할 수있는 시퀀스를 반환하는 것보다 낫습니다.

+0

좋은 제안. 감사. 나는 for 루프에서 Thread.Sleep (정말로 긴 시간)을 호출하는 것과 같은 의미가 아니라면, 컬렉션을 완전히 열거하지 않은 스레드가 잠금을 유지할 수있는 방법이 궁금하다. 그렇지 않으면 루프에서 빠져 나와 열거 자에서 Dispose를 호출하고 잠금을 해제합니다. – Daniel

+0

@ 대니얼 - 자물쇠를 풀지 않는 예를 추가했습니다. 호출 코드가 항상 for 루프를 사용한다면 아마도 안전 할 것이지만 IEnumerables를 사용하는 다른 방법이 있습니다. – kvb

+0

추가 설명 주셔서 감사합니다. – Daniel

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 키워드를 사용하여 lock과 같은 C#을 구현합니다.이 속성은 비슷한 속성 (범위를 벗어날 때 무엇인가 할 수 있음)을 갖습니다. 따라서 원래의 C# 버전의 코드에 훨씬 가깝습니다.