2014-07-17 2 views
1

최근에 C#에서 제공하는 UpgradeableReadLock 구조가 생각 나게되어 실제로 사용할 때 분별하려고합니다.UpgradeableReadLock에 대한 적법한 사용 사례

예를 들어 많은 클래스에서 많이 읽히는 설정 캐시가 있지만 결정적이지 않은 조건 집합을 기반으로 주기적으로 매우 낮은 빈도로 업데이트해야합니다.

List<Setting> cachedSettings = this.GetCachedSettings(sessionId); 

lock(cachedSettings) 
{ 
    bool requiresRefresh = cachedSettings.RequiresUpdate(); 
    if(requiresRefresh) 
    { 
     // a potentially long operation 
     UpdateSettings(cachedSettings, sessionId); 
    } 

    return cachedSettings; 
} 

을하거나 UpgradeableReadLock을 사용합니다 :

는 단순히과 같이 고정하는 것이 더 나을

public class SomeRepitory { 

private ReaderWriterLockSlim _rw = new ReaderWriterLockSlim(); 

public List<Setting> GetCachedSettings(string sessionId) 
{ 
    _rw.EnterUpgradeableReadLock(); 

    List<Setting> cachedSettings = this.GetCachedSettings(sessionId); 

    bool requiresRefresh = cachedSettings.RequiresUpdate(); 
    if(requiresRefresh) 
    { 
     _rw.EnterWriteLock(); 

     UpdateSettings(cachedSettings, sessionId); 

     _rw.ExitWriteLock(); 
    } 

    _rw.ExitUpgradeableReadLock(); 

    return cachedSettings; 
} 

아마도 나를 혼란 무엇 대부분은 쓰기 블록 외부에서 업데이트가 필요한지 확인하는 방법입니다.

while (true) 
{ 
    int newNumber = GetRandNum (100); 
    _rw.EnterUpgradeableReadLock(); 
    if (!_items.Contains (newNumber)) 
    { 
     _rw.EnterWriteLock(); 
     _items.Add (newNumber); 
     _rw.ExitWriteLock(); 
     Console.WriteLine ("Thread " + threadID + " added " + newNumber); 
    } 
    _rw.ExitUpgradeableReadLock(); 
    Thread.Sleep (100); 
} 

나의 이해는이 동시에 허용되는

: 내 위의 예에서 나는 새로 고침이 요구되는 검사,하지만 난 "C# 5.0 간단히 말해서"의 예를 사용합니다 단순화 할 때 말하는거다 쓰레드가 쓰여질 필요가 없다면 읽지 만 둘 이상의 쓰레드가 같은 난수로 끝나고 !_items.Contains(newNumber)을 결정한다면? 이 이해를 감안할 때 이것은 동시 읽기 허용해야합니다 (물론 내가 잘못 이해 한 경우 올바른) .. 쓰기 잠금을 얻 자마자 동시에 읽을 수있는 모든 스레드가 일시 중단되고 강제로 다시 시작해야합니다. _rw.EnterUpgradeableReadLock();의 시작 부분에?

답변

2

당연히 두 번째 방법은 많은 동시 판독기와 비교적 드문 쓰기 작업의 경우에 더 좋습니다. 읽기 잠금이 스레드에 의해 획득되면 (_rw.EnterUpgradeableReadLock() 사용) 다른 스레드도이를 획득하여 동시에 값을 읽을 수 있습니다. 그런 다음 일부 스레드가 쓰기 잠금을 입력하면 모든 읽기가 완료 될 때까지 기다린 다음 잠금 객체 (EnterXXX() 작업 대기를 실행하려는 다른 모든 스레드)에 대한 배타적 액세스를 획득하여 값을 업데이트합니다. 잠금을 해제하면 다른 스레드가 작업을 수행 할 수 있습니다.

첫 번째 예제 lock(cachedSettings)은 다른 스레드를 모두 차단하므로 한 번에 하나의 스레드 만 값을 읽을 수 있습니다. 모든 입력/종료 잠금 작업을위한

_rw.EnterUpgradeableReadLock(); 
try 
{ 
    //Do your job 
} 
finally 
{ 
    _rw.ExitUpgradeableReadLock(); 
} 

:

나는 다음과 같은 패턴을 사용 이외에 추천 할 것입니다. 동기화 코드 내에서 예외가 발생하면 잠금이 영원히 잠긴 상태로 유지되지 않습니다 (with high probability).

편집 : 답변 마틴의 의견. 여러 스레드에서 동시에 값을 업데이트하지 않으려면이를 구현하기 위해 논리를 변경해야합니다. 예를 들어, double-checked lock 구조를 사용하여 :

if(cachedSettings.RequiresUpdate()) 
{ 
    _rw.EnterWriteLock(); 
    try 
    { 
     if(cachedSettings.RequiresUpdate()) 
     { 
      UpdateSettings(cachedSettings, sessionId); 
     } 
    } 
    finally 
    { 
     _rw.ExitWriteLock(); 
    } 
} 

우리가 쓰기 잠금 다른 스레드를 기다리는 동안하는 것은 이미 그 값을 갱신하지 않은 경우이 확인됩니다. 그리고 값이 새로 고침을 더 이상 필요로하지 않는다면 - 그냥 잠금을 해제하십시오.

중요 : 오랫동안 단독 잠금을 사용하는 것은 대단히 어렵습니다. 따라서 UpdateSettings 함수는 장기 실행이므로 자물쇠 외부에서 실행하고 일부 스레드가 새로 고치는 동안 독자가 ​​만료 값을 읽을 수 있도록 일부 추가 로직을 구현하는 것이 좋습니다.캐시를 한 번 구현하는 데 사용되었으므로 신속하고 스레드로부터 안전하게 만들려면 정말 복잡합니다. 기존 구현 (예 : System.Runtime.MemoryCache) 중 하나를 사용하는 것이 좋습니다.

+0

하지만 동일한 세션/컨텍스트에 대해 두 개의 스레드가 쓰기 잠금을 얻기 전에 RequiresUpdate를 확인하고 requiresRefresh 값이 true 인 두 개의 스레드로 끝나면 ... 그러면 UpdateSettings 메서드 인 a 잠재적으로 비교적 오래 실행하는 방법, 결국 두 번 전화 받고? – Jordan

+1

쓰기 잠금 스레드가 모든 읽기 잠금을 완료 할 때까지 대기하는 경우 @Martin이 게시 한 코드는 _items가 비어 있고 스레드 1과 2가 판독기 잠금을 입력하고 스레드 1은 쓰기 잠금을 입력하려고 시도하고 스레드 2가 대기합니다. 완료되면 스레드 2도 쓰기 잠금을 입력하려고 시도하고 완료되지 않음 - 데드락. 아니면 내가 틀렸어? 편집 : Conclustions 잠금 (chacheSettings)은 느리지 만 방탄입니다 :) –

+0

@RytisI, 아주 좋은 질문입니다! 그러나 나는이 패턴을 많이 사용했고 그러한 시나리오에서는 결코 교착 상태를 보지 못했습니다. 필자는 이것이 EnterUpgradableReadLock()이 EnterReadlock()과 별도로 존재하는 이유라고 생각합니다. 업그레이드 가능한 잠금 장치가 WriteLock()을 입력하면 쓰기 큐에있는 작성기 (있는 경우)에 대한 잠금을 해제합니다. 대기열에 기록기가 없으면 쓰기 잠금을 획득합니다. 비록이 행동에서 나는 잘 모르겠지만. 그냥 추측 ... –