[C#] .NET Framework에서 동시 HashSet <T>?

다음 수업이 있습니다.

class Test{
    public HashSet<string> Data = new HashSet<string>();
}

다른 스레드에서 “데이터”필드를 변경해야하므로 현재 스레드 안전 구현에 대한 의견이 필요합니다.

class Test{
    public HashSet<string> Data = new HashSet<string>();

    public void Add(string Val){
            lock(Data) Data.Add(Val);
    }

    public void Remove(string Val){
            lock(Data) Data.Remove(Val);
    }
}

현장으로 직접 이동하여 여러 스레드가 동시에 액세스하지 못하도록하는 더 나은 솔루션이 있습니까?



답변

구현이 정확합니다. 불행히도 .NET Framework는 내장 동시 해시 셋 유형을 제공하지 않습니다. 그러나 몇 가지 해결 방법이 있습니다.

동시 사전 (권장)

첫 번째 ConcurrentDictionary<TKey, TValue>는 네임 스페이스 에서 클래스를 사용하는 것 System.Collections.Concurrent입니다. 이 경우 값이 의미가 없으므로 단순 byte(메모리에서 1 바이트)을 사용할 수 있습니다 .

private ConcurrentDictionary<string, byte> _data;

유형은 스레드로부터 안전 HashSet<T>하고 키와 값이 다른 객체와 동일한 이점을 제공하기 때문에 권장되는 옵션 입니다.

출처 : 소셜 MSDN

동시 가방

중복 항목이 마음에 들지 않으면 ConcurrentBag<T>이전 클래스와 동일한 네임 스페이스에서 클래스 를 사용할 수 있습니다 .

private ConcurrentBag<string> _data;

자기 구현

마지막으로, 잠금이나 .NET이 스레드 안전을 제공하는 다른 방법을 사용하여 자체 데이터 형식을 구현할 수 있습니다. 다음은 좋은 예입니다. .Net에서 ConcurrentHashSet을 구현하는 방법

이 솔루션의 유일한 단점은 HashSet<T>읽기 작업의 경우에도 형식 이 공식적으로 동시 액세스하지 않는다는 것입니다.

링크 된 게시물의 코드를 인용하십시오 (원래 Ben Mosher 작성 ).

using System;
using System.Collections.Generic;
using System.Threading;

namespace BlahBlah.Utilities
{
    public class ConcurrentHashSet<T> : IDisposable
    {
        private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
        private readonly HashSet<T> _hashSet = new HashSet<T>();

        #region Implementation of ICollection<T> ...ish
        public bool Add(T item)
        {
            _lock.EnterWriteLock();
            try
            {
                return _hashSet.Add(item);
            }
            finally
            {
                if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
            }
        }

        public void Clear()
        {
            _lock.EnterWriteLock();
            try
            {
                _hashSet.Clear();
            }
            finally
            {
                if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
            }
        }

        public bool Contains(T item)
        {
            _lock.EnterReadLock();
            try
            {
                return _hashSet.Contains(item);
            }
            finally
            {
                if (_lock.IsReadLockHeld) _lock.ExitReadLock();
            }
        }

        public bool Remove(T item)
        {
            _lock.EnterWriteLock();
            try
            {
                return _hashSet.Remove(item);
            }
            finally
            {
                if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
            }
        }

        public int Count
        {
            get
            {
                _lock.EnterReadLock();
                try
                {
                    return _hashSet.Count;
                }
                finally
                {
                    if (_lock.IsReadLockHeld) _lock.ExitReadLock();
                }
            }
        }
        #endregion

        #region Dispose
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
                if (_lock != null)
                    _lock.Dispose();
        }
        ~ConcurrentHashSet()
        {
            Dispose(false);
        }
        #endregion
    }
}

편집 : 입구 잠금 방법을 이동하면 try예외가 발생하고 finally블록에 포함 된 명령을 실행할 수 있으므로 블록을 ouside하십시오 .


답변

를 감싸 ConcurrentDictionary거나 잠그는 대신 HashSet실제 ConcurrentHashSet기반으로 만들었습니다 ConcurrentDictionary.

이 구현은 HashSet동시 시나리오 IMO에서 덜 이해하기 때문에 설정 작업 없이 항목 당 기본 작업을 지원합니다 .

var concurrentHashSet = new ConcurrentHashSet<string>(
    new[]
    {
        "hamster",
        "HAMster",
        "bar",
    },
    StringComparer.OrdinalIgnoreCase);

concurrentHashSet.TryRemove("foo");

if (concurrentHashSet.Contains("BAR"))
{
    Console.WriteLine(concurrentHashSet.Count);
}

출력 : 2

NuGet 에서 구할 수 있으며 여기 GitHub의 소스를 볼 수 있습니다 .


답변

다른 사람이 언급하지 않았으므로 귀하의 특정 목적에 적합하거나 적합하지 않은 대체 방법을 제공 할 것입니다.

Microsoft 불변 컬렉션

MS 팀 의 블로그 게시물 에서 :

동시 생성 및 실행이 그 어느 때보 다 쉬워 지지만 여전히 근본적인 문제 중 하나 인 가변 공유 상태가 여전히 존재합니다. 여러 스레드에서 읽는 것은 일반적으로 매우 쉽지만 일단 상태를 업데이트해야하는 경우 특히 잠금이 필요한 설계에서는 훨씬 어려워집니다.

잠금의 대안은 불변 상태를 이용하는 것입니다. 불변의 데이터 구조는 절대 변경되지 않으므로 다른 사람의 발가락을 밟을 염려없이 다른 스레드간에 자유롭게 전달할 수 있습니다.

이 디자인은 새로운 문제를 만듭니다. 매번 전체 상태를 복사하지 않고 상태 변경을 어떻게 관리합니까? 컬렉션이 관련된 경우 특히 까다 롭습니다.

불변의 컬렉션이 들어온 곳입니다.

이러한 컬렉션에는 ImmutableHashSet <T>ImmutableList <T>가 포함 됩니다.

공연

변경 불가능한 콜렉션은 아래의 트리 데이터 구조를 사용하여 구조 공유를 가능하게하기 때문에 성능 특성이 변경 가능한 콜렉션과 다릅니다. 잠금 변경 가능 콜렉션과 비교할 때 결과는 잠금 경합 및 액세스 패턴에 따라 다릅니다. 그러나 불변 컬렉션에 대한 다른 블로그 게시물 에서 가져온 것 입니다.

Q : 불변의 컬렉션이 느리다고 들었습니다. 이것들은 다른가요? 성능이나 메모리가 중요한 경우 사용할 수 있습니까?

A :이 불변 컬렉션은 메모리 공유의 균형을 유지하면서 변경 가능한 컬렉션에 대해 경쟁적인 성능 특성을 갖도록 크게 조정되었습니다. 어떤 경우에는 알고리즘 및 실제 시간 모두에서 가변 컬렉션만큼 빠르며 때로는 더 빠르며 다른 경우에는 알고리즘 적으로 더 복잡합니다. 그러나 많은 경우에 그 차이는 무시할 수 있습니다. 일반적으로 가장 간단한 코드를 사용하여 작업을 완료 한 다음 필요에 따라 성능을 조정해야합니다. 불변 컬렉션은 특히 스레드 안전성을 고려해야하는 경우 간단한 코드를 작성하는 데 도움이됩니다.

다시 말해서, 많은 경우에 차이는 눈에 띄지 ImmutableHashSet<T>않으며 기존 잠금 변경 가능 구현이 없기 때문에 동시 세트의 경우 더 간단한 선택을 사용해야 합니다! 🙂


답변

ISet<T>동시성을 만드는 데있어 까다로운 부분은 설정된 메소드 (연합, 교차점, 차이)가 본질적으로 반복적이라는 것입니다. 최소한 두 세트를 잠그는 동안 조작에 포함 된 세트 중 하나의 n 개 멤버 전체를 반복해야합니다.

ConcurrentDictionary<T,byte>반복하는 동안 전체 세트를 잠 가야 하는 경우 에는 이점이 없습니다 . 잠금이 없으면 이러한 작업은 스레드 안전이 아닙니다.

의 추가 오버 헤드가 주어지면 ConcurrentDictionary<T,byte>더 가벼운 무게를 사용하고 HashSet<T>자물쇠로 모든 것을 감싸는 것이 현명 합니다.

설정 조작이 필요하지 않은 경우 키를 추가 할 때 값을 사용 ConcurrentDictionary<T,byte>하고 사용 default(byte)하십시오.


답변

나는 완벽한 솔루션을 선호하므로 이렇게했다 : 내 카운트는 다른 방식으로 구현된다는 것을 명심하십시오. 왜냐하면 그 값을 계산하는 동안 해시 세트를 읽는 것이 금지되어야하는 이유를 알 수 없기 때문입니다.

@ 젠, 시작해 주셔서 감사합니다.

[DebuggerDisplay("Count = {Count}")]
[Serializable]
public class ConcurrentHashSet<T> : ICollection<T>, ISet<T>, ISerializable, IDeserializationCallback
{
    private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

    private readonly HashSet<T> _hashSet = new HashSet<T>();

    public ConcurrentHashSet()
    {
    }

    public ConcurrentHashSet(IEqualityComparer<T> comparer)
    {
        _hashSet = new HashSet<T>(comparer);
    }

    public ConcurrentHashSet(IEnumerable<T> collection)
    {
        _hashSet = new HashSet<T>(collection);
    }

    public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer)
    {
        _hashSet = new HashSet<T>(collection, comparer);
    }

    protected ConcurrentHashSet(SerializationInfo info, StreamingContext context)
    {
        _hashSet = new HashSet<T>();

        // not sure about this one really...
        var iSerializable = _hashSet as ISerializable;
        iSerializable.GetObjectData(info, context);
    }

    #region Dispose

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
            if (_lock != null)
                _lock.Dispose();
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _hashSet.GetEnumerator();
    }

    ~ConcurrentHashSet()
    {
        Dispose(false);
    }

    public void OnDeserialization(object sender)
    {
        _hashSet.OnDeserialization(sender);
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        _hashSet.GetObjectData(info, context);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion

    public void Add(T item)
    {
        _lock.EnterWriteLock();
        try
        {
            _hashSet.Add(item);
        }
        finally
        {
            if(_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public void UnionWith(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        _lock.EnterReadLock();
        try
        {
            _hashSet.UnionWith(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
            if (_lock.IsReadLockHeld) _lock.ExitReadLock();
        }
    }

    public void IntersectWith(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        _lock.EnterReadLock();
        try
        {
            _hashSet.IntersectWith(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
            if (_lock.IsReadLockHeld) _lock.ExitReadLock();
        }
    }

    public void ExceptWith(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        _lock.EnterReadLock();
        try
        {
            _hashSet.ExceptWith(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
            if (_lock.IsReadLockHeld) _lock.ExitReadLock();
        }
    }

    public void SymmetricExceptWith(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        try
        {
            _hashSet.SymmetricExceptWith(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool IsSubsetOf(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.IsSubsetOf(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool IsSupersetOf(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.IsSupersetOf(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool IsProperSupersetOf(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.IsProperSupersetOf(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool IsProperSubsetOf(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.IsProperSubsetOf(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool Overlaps(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.Overlaps(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool SetEquals(IEnumerable<T> other)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.SetEquals(other);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    bool ISet<T>.Add(T item)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.Add(item);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public void Clear()
    {
        _lock.EnterWriteLock();
        try
        {
            _hashSet.Clear();
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool Contains(T item)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.Contains(item);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        _lock.EnterWriteLock();
        try
        {
            _hashSet.CopyTo(array, arrayIndex);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public bool Remove(T item)
    {
        _lock.EnterWriteLock();
        try
        {
            return _hashSet.Remove(item);
        }
        finally
        {
            if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
        }
    }

    public int Count
    {
        get
        {
            _lock.EnterWriteLock();
            try
            {
                return _hashSet.Count;
            }
            finally
            {
                if(_lock.IsWriteLockHeld) _lock.ExitWriteLock();
            }

        }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }
}


답변