我想实现一个线程安全列表,但是必须在整个操作块上确保线程安全,而不仅仅是单个操作(例如,添加,删除等)。用例应如下所示:< / p>
list.Lock();
list.Add(sth);
list.RemoveAt(4);
list.Unlock();
我希望列表要求锁定任何操作。例如:
list.Add(sth);
在没有事先锁定的情况下调用会导致异常。这就是我不使用lock()
语句的原因 - 锁定检查对于此解决方案至关重要。
使用Monitor实现这样的列表并不难 - 但只有在想要检查列表是否被锁定之前。我想到了以下场景:
// 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;
}
不幸的是,此代码很容易出现竞争条件 - 无论在进入或离开关键部分之前或之后是否设置了locked
。
另一种使用TryEnter的方法,但是如果没有获得锁定,这种方法实际上会进入临界区,这也可能导致竞争条件。
我应该如何实现这种机制是线程安全的,以及在检查列表是否被锁定时如何避免竞争条件?
答案 0 :(得分:3)
我正在考虑一些类似于Builder模式的东西。
使用示例:
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.
由end()
执行的锁定获取可以是对sync {...}
的简单调用 - 没有可能的竞争条件。
答案 1 :(得分:3)
您可以让您的类负责锁定,并且只公开一个接受消费者指定的Action
的方法:
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);
}
}
}
}
消费者可以在task
内拥有他们喜欢的任何操作序列,并且您知道已经锁定了 - 因为您自己接受了。
答案 2 :(得分:1)
如果为用作锁的对象提供getter,则可以使用标准锁定机制。 虽然我认为这不是一个优雅的解决方案,但它肯定会成功。
void performOpertations(TSList list) {
lock(list.getLock()) {
list.add(obj);
list.remove(obj2);
// do your stuff
}
}
由于C#synchronized块是可重入的,因此您可以在示例中调用列表的内部同步方法,如add()
或remove()
。
另一个解决方案是使用visitor pattern 并让访问者操作被synchronized块包围。
答案 3 :(得分:1)
我看不出有任何理由不使用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;
}
}
}