스레드 안전 사전을 구현하는 가장 좋은 방법은 무엇입니까?
IDictionary에서 파생하고 개인 SyncRoot 개체를 정의하여 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...
}
Edit: The MSDN docs point out that enumerating is inherently not thread safe. That can be one reason for exposing a synchronization object outside your class. Another way to approach that would be to provide some methods for performing an action on all members and lock around the enumerating of the members. The problem with this is that you don't know if the action passed to that function calls some member of your dictionary (that would result in a deadlock). Exposing the synchronization object allows the consumer to make those decisions and doesn't hide the deadlock inside your class.
The .NET 4.0 class that supports concurrency is named ConcurrentDictionary
.
Attempting to synchronize internally will almost certainly be insufficient because it's at too low a level of abstraction. Say you make the Add
and ContainsKey
operations individually thread-safe as follows:
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);
}
}
Then what happens when you call this supposedly thread-safe bit of code from multiple threads? Will it always work OK?
if (!mySafeDictionary.ContainsKey(someKey))
{
mySafeDictionary.Add(someKey, someValue);
}
The simple answer is no. At some point the Add
method will throw an exception indicating that the key already exists in the dictionary. How can this be with a thread-safe dictionary, you might ask? Well just because each operation is thread-safe, the combination of two operations is not, as another thread could modify it between your call to ContainsKey
and Add
.
Which means to write this type of scenario correctly you need a lock outside the dictionary, e.g.
lock (mySafeDictionary)
{
if (!mySafeDictionary.ContainsKey(someKey))
{
mySafeDictionary.Add(someKey, someValue);
}
}
But now, seeing as you're having to write externally locking code, you're mixing up internal and external synchronisation, which always leads to problems such as unclear code and deadlocks. So ultimately you're probably better to either:
Use a normal
Dictionary<TKey, TValue>
and synchronize externally, enclosing the compound operations on it, orWrite a new thread-safe wrapper with a different interface (i.e. not
IDictionary<T>
) that combines the operations such as anAddIfNotContained
method so you never need to combine operations from it.
(I tend to go with #1 myself)
You shouldn't publish your private lock object through a property. The lock object should exist privately for the sole purpose of acting as a rendezvous point.
If performance proves to be poor using the standard lock then Wintellect's Power Threading collection of locks can be very useful.
There are several problems with implementation method you are describing.
- You shouldn't ever expose your synchronization object. Doing so will open up yourself to a consumer grabbing the object and taking a lock on it and then you're toast.
- You're implementing a non-thread safe interface with a thread safe class. IMHO this will cost you down the road
Personally, I've found the best way to implement a thread safe class is via immutability. It really reduces the number of problems you can run into with thread safety. Check out Eric Lippert's Blog for more details.
You don't need to lock the SyncRoot property in your consumer objects. The lock you have within the methods of the dictionary is sufficient.
To Elaborate: What ends up happening is that your dictionary is locked for a longer period of time than is necessary.
What happens in your case is the following:
Say thread A acquires the lock on SyncRoot before the call to m_mySharedDictionary.Add. Thread B then attempts to acquire the lock but is blocked. In fact, all other threads are blocked. Thread A is allowed to call into the Add method. At the lock statement within the Add method, thread A is allowed to obtain the lock again because it already owns it. Upon exiting the lock context within the method and then outside the method, thread A has released all locks allowing other threads to continue.
You can simply allow any consumer to call into the Add method as the lock statement within your SharedDictionary class Add method will have the same effect. At this point in time, you have redundant locking. You would only lock on SyncRoot outside of one of the dictionary methods if you had to perform two operations on the dictionary object that needed to be guaranteed to occur consecutively.
Just a thought why not recreate the dictionary? If reading is a multitude of writing then locking will synchronize all requests.
example
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);
}
'Programming' 카테고리의 다른 글
git : 한 리포지토리의 커밋에 의해 도입 된 변경 사항을 다른 리포지토리에 적용 (0) | 2020.08.06 |
---|---|
부트 스트랩 : 컨테이너의 너비를 어떻게 변경합니까? (0) | 2020.08.06 |
경계 상자에 맞게 이미지 크기 조정 (0) | 2020.08.06 |
iOS에서 ISO 8601 날짜를 얻으려면 어떻게합니까? (0) | 2020.08.06 |
다음 기능을 표현합니다. 실제로 무엇을위한 것입니까? (0) | 2020.08.06 |