How to implement a thread safety list?

I want to implement a thread safety list, but thread safety should be ensured on the entire block of operations not only on one operation (for example, add, delete, etc.). A usage example should look like this:

list.Lock(); list.Add(sth); list.RemoveAt(4); list.Unlock(); 

I want the list to require locking for any operations. For instance:

 list.Add(sth); 

called without preliminary blocking should result in an exception. This is why I do not use the lock() operator - checking for locks is critical to this solution.

Implementing such a list using Monitor is not a difficult task - but only until you want to check whether the list is locked or not. I thought of the following scenario:

 // Inside list class private object lockObject; private bool locked; public void Lock() { Monitor.Enter(lockObject); locked = true; } public void Unlock() { Monitor.Exit(lockObject); locked = false; } 

Unfortunately, this code is subject to race conditions - regardless of whether it is locked before or after entering or exiting a critical section.

Another approach related to the use of TryEnter, but this method is really included in the critical section if no lock is obtained, which can also lead to race conditions.

How should I implement this mechanism to ensure thread safety and how to avoid race conditions while checking if the list is locked or not?

+4
source share
4 answers

You can make your class responsible for locking and set only one method that takes Action , which the consumer indicates:

 public class LockedCollection { private class CollectionImpl : ICollection<int> { //Collection methods } private sealed class CollectionWrapper : ICollection<int>, IDisposable { public CollectionWrapper(CollectionImpl inner) { _inner = inner; } private CollectionImpl _inner //Collection methods all just wrapping calls to inner public void Dispose() { _inner = null; } } private CollectionImpl _instance - new CollectionImpl(); private object _lock = new object(); public void DoStuff(Action<ICollection<int>> task) { lock(_lock) { using(var wrapper = new CollectionWrapper(_instance)) { task(wrapper); } } } } 

The consumer can have any sequence of operations that they like inside the task , and you know that the lock is completed - because you took it yourself.

+3
source

I am thinking of something that will look like a Builder pattern.

Usage example:

 list .do() // mandatory initial statement. // Doesn't acquire a lock - it just builds a transaction object .add(42) // Add an operation to the transaction object. // Call would be illegal without performing do() before .removeAt(0) // Another added operation .end(); // Acquire the lock, perform the specified changes, release the lock. 

The lock capture performed by end() may be a simple call to sync {...} - there are no possible race conditions.

+3
source

You can use the standard locking mechanism if you provide a getter for an object used as a lock. Although I do not think this is an elegant solution, it will certainly do the trick.

 void performOpertations(TSList list) { lock(list.getLock()) { list.add(obj); list.remove(obj2); // do your stuff } } 

Since C # synchronized blocks are reentrant, you can invoke the in-synchronous list method, for example add() or remove() in this example.

Another solution would be to use a visitor template and have visitor actions surrounded by a synchronized block.

+1
source

I see no reason not to use ReaderWriterLockSlim :

 class MyThreadSafeList<T> { private readonly List<T> internalList = new List<T>(); private readonly ReaderWriterLockSlim lockSlim = new ReaderWriterLockSlim(); public void EnterReadLock() { lockSlim.EnterReadLock(); } public void ExitReadLock() { lockSlim.ExitReadLock(); } public void EnterWriteLock() { lockSlim.EnterWriteLock(); } public void ExitWriteLock() { lockSlim.ExitWriteLock(); } public void Add(T item) { if (!lockSlim.IsWriteLockHeld) { throw new InvalidOperationException(); } internalList.Add(item); } public void Remove(T item) { if (!lockSlim.IsWriteLockHeld) { throw new InvalidOperationException(); } internalList.Remove(item); } public T this[int index] { get { if (!lockSlim.IsReadLockHeld) { throw new InvalidOperationException(); } return internalList[index]; } set { if (!lockSlim.IsWriteLockHeld) { throw new InvalidOperationException(); } internalList[index] = value; } } } 
+1
source

Source: https://habr.com/ru/post/1485560/


All Articles