문제

우연히 유명한 객체를 정의하여 C#에서 스레드 안전 사전을 구현할 수있었습니다.

public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
    private readonly object syncRoot = new object();
    private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();

    public object SyncRoot
    {
        get { return syncRoot; }
    } 

    public void Add(TKey key, TValue value)
    {
        lock (syncRoot)
        {
            d.Add(key, value);
        }
    }

    // more IDictionary members...
}

그런 다음 소비자 (다중 스레드) 전체에 걸쳐이 Syncroot 객체를 고정합니다.

예시:

lock (m_MySharedDictionary.SyncRoot)
{
    m_MySharedDictionary.Add(...);
}

나는 그것을 작동시킬 수 있었지만 이로 인해 추악한 코드가 생겼습니다. 내 질문은, 스레드 안전 사전을 구현하는 더 좋고 우아한 방법이 있습니까?

도움이 되었습니까?

해결책

Peter가 말했듯이, 수업 내에서 모든 스레드 안전을 캡슐화 할 수 있습니다. 노출 또는 추가 이벤트에주의를 기울여야하며 자물쇠 외부에서 호출되도록해야합니다.

public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
    private readonly object syncRoot = new object();
    private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();

    public void Add(TKey key, TValue value)
    {
        lock (syncRoot)
        {
            d.Add(key, value);
        }
        OnItemAdded(EventArgs.Empty);
    }

    public event EventHandler ItemAdded;

    protected virtual void OnItemAdded(EventArgs e)
    {
        EventHandler handler = ItemAdded;
        if (handler != null)
            handler(this, e);
    }

    // more IDictionary members...
}

편집하다: MSDN 문서는 열거가 본질적으로 스레드 안전하지 않다고 지적합니다. 이것이 클래스 밖에서 동기화 객체를 노출시키는 이유 중 하나 일 수 있습니다. 접근하는 또 다른 방법은 모든 회원에게 행동을 수행하는 몇 가지 방법을 제공하고 회원의 열거를 중단하는 것입니다. 이것의 문제점은 해당 기능에 동작이 전달되는지 여부를 모른다는 것입니다. 동기화 객체를 노출하면 소비자는 해당 결정을 내릴 수 있으며 클래스 내에서 교착 상태를 숨기지 못합니다.

다른 팁

동시성을 지원하는 .NET 4.0 클래스의 이름이 지정되었습니다 ConcurrentDictionary.

내부적으로 동기화하려는 시도는 너무 낮은 수준의 추상화이기 때문에 거의 불충분합니다. 당신이 만든다고 말합니다 Add 그리고 ContainsKey 다음과 같이 개별적으로 스레드 안전을 조작합니다.

public void Add(TKey key, TValue value)
{
    lock (this.syncRoot)
    {
        this.innerDictionary.Add(key, value);
    }
}

public bool ContainsKey(TKey key)
{
    lock (this.syncRoot)
    {
        return this.innerDictionary.ContainsKey(key);
    }
}

그렇다면 여러 스레드 에서이 스레드 안전 비트의 코드라고 할 때 어떻게됩니까? 항상 잘 작동할까요?

if (!mySafeDictionary.ContainsKey(someKey))
{
    mySafeDictionary.Add(someKey, someValue);
}

간단한 대답은 아니오입니다. 어느 시점에서 Add 메소드는 키가 이미 사전에 존재한다는 것을 나타내는 예외를 던집니다. 스레드 안전 사전으로 어떻게 될 수 있습니까? 각 작업이 스레드 안전이기 때문에 다른 스레드는 다른 스레드를 호출 사이에 수정할 수 있으므로 두 작업의 조합이 아닙니다. ContainsKey 그리고 Add.

이 유형의 시나리오를 올바르게 작성하려면 잠금이 필요합니다. 밖의 사전, 예를 들어

lock (mySafeDictionary)
{
    if (!mySafeDictionary.ContainsKey(someKey))
    {
        mySafeDictionary.Add(someKey, someValue);
    }
}

그러나 이제 외부 잠금 코드를 작성해야 할 때 내부 및 외부 동기화를 혼합하여 항상 불분명 한 코드 및 교착 상태와 같은 문제로 이어집니다. 따라서 궁극적으로 당신은 아마도 다음 중 하나가 더 낫습니다.

  1. 정상을 사용하십시오 Dictionary<TKey, TValue> 외부에서 동기화하거나 화합물 작업을 동반하거나

  2. 다른 인터페이스가있는 새 스레드 안전 래퍼를 작성하십시오 (예 : IDictionary<T>)와 같은 작업을 결합합니다 AddIfNotContained 방법으로 작업을 결합 할 필요가 없습니다.

(나는 #1 직접가는 경향이있다)

속성을 통해 개인 잠금 객체를 게시해서는 안됩니다. 잠금 객체는 랑데부 포인트 역할을하는 유일한 목적으로 개인적으로 존재해야합니다.

표준 잠금을 사용하여 성능이 좋지 않은 경우 WintEllect의 파워 스레딩 자물쇠 수집은 매우 유용 할 수 있습니다.

구현 방법에는 몇 가지 문제가 있습니다.

  1. 동기화 객체를 노출해서는 안됩니다. 그렇게하면 소비자가 물체를 잡고 잠그면 토스트를 할 수 있습니다.
  2. 스레드 안전 클래스와 함께 비 스레드 안전 인터페이스를 구현하고 있습니다. IMHO 이것은 비용이들 것입니다

개인적으로, 스레드 안전 클래스를 구현하는 가장 좋은 방법은 불변성을 통한 것입니다. 스레드 안전을 통해 발생할 수있는 문제의 수를 실제로 줄입니다. 체크 아웃 Eric Lippert의 블로그 자세한 사항은.

소비자 개체에서 SynCroot 속성을 잠글 필요는 없습니다. 사전의 방법 내에있는 자물쇠만으로도 충분합니다.

자세히 설명하기 :끝나는 것은 사전이 필요한 것보다 더 오랜 시간 동안 잠겨 있다는 것입니다.

귀하의 경우에 발생하는 일은 다음과 같습니다.

say stlead a는 syncroot의 잠금을 얻습니다 ~ 전에 m_mysharedDictionary.add에 대한 호출. 그런 다음 스레드 B는 잠금을 획득하려고 시도하지만 차단됩니다. 실제로 다른 모든 스레드가 차단됩니다. 스레드 A는 추가 메소드로 호출 할 수 있습니다. ADD 메소드 내의 잠금 문에서 스레드 A는 이미 소유하고 있으므로 잠금을 다시 얻을 수 있습니다. 메소드 내에서 잠금 컨텍스트를 종료 한 다음 메소드 외부에서 나가면 스레드 A는 모든 잠금 장치를 해제하여 다른 스레드를 계속할 수 있습니다.

SharedDictionary Class ADD 메소드 내의 잠금 문이 동일한 효과를 갖기 때문에 모든 소비자가 추가 메소드를 호출하도록 허용 할 수 있습니다. 이 시점에서, 당신은 중복 잠금이 있습니다. 연속적으로 발생하도록 보장 해야하는 사전 객체에서 두 개의 작업을 수행 해야하는 경우 사전 방법 중 하나 외부에서 SynCroot 만 고정합니다.

사전을 재현하지 않는 이유는 무엇입니까? 읽기가 다수의 쓰기 인 경우 잠금이 모든 요청을 동기화합니다.

예시

    private static readonly object Lock = new object();
    private static Dictionary<string, string> _dict = new Dictionary<string, string>();

    private string Fetch(string key)
    {
        lock (Lock)
        {
            string returnValue;
            if (_dict.TryGetValue(key, out returnValue))
                return returnValue;

            returnValue = "find the new value";
            _dict = new Dictionary<string, string>(_dict) { { key, returnValue } };

            return returnValue;
        }
    }

    public string GetValue(key)
    {
        string returnValue;

        return _dict.TryGetValue(key, out returnValue)? returnValue : Fetch(key);
    }
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top