【问题标题】:How to implement threadsafe list?如何实现线程安全列表?
【发布时间】:2013-06-11 06:42:26
【问题描述】:

我想实现一个线程安全列表,但必须确保整个操作块的线程安全,而不仅仅是单个操作(例如添加、删除等)。用例应如下所示:

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,但是这种方法实际上是在没有获得锁的情况下进入了临界区,这也可能导致竞争条件。

我应该如何实现这个机制以保证线程安全以及如何在检查列表是否被锁定时避免竞争条件?

【问题讨论】:

  • 暂且不说我提出的解决方案,我想说您的实现问题可以通过使用 compareAndSet 来解决(我不确定它在 C# 世界中是如何调用的)
  • 作为旁注,这些有帮助吗? Thread-Safe Collections

标签: c# list concurrency thread-safety


【解决方案1】:

您可以让 您的 类负责锁定,并且只公开一个接受消费者指定的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 中进行任何他们喜欢的操作序列,并且您知道锁已被占用 - 因为您自己占用了它。

【讨论】:

  • 我非常喜欢这个解决方案,但这并不完全安全:有人可能会在受保护的块之外破解自己:lc.DoStuff( (ICollection&lt;int&gt; c) =&gt; { localVariable = c; } );
  • @Spook - 实际上,您可以通过创建另一个实现ICollection 并充当真实集合的包装器的类来处理该问题。您创建一个新的,将 that 传递给task,当task 返回时,Dispose/使包装类无效,使其无法再访问该集合。 (确保 dispose/invalidate 在 finally 块中)
  • 您的解决方案似乎提供了原子性,但没有跨操作集的事务性。
  • @vemv - 应该一起发生的一组操作应该放在一个 single Action
  • 哦,这个命名误导了我。无论如何,让Action 成为一个“标量”操作并传递一个动作列表对我来说似乎是一个更好的设计。
【解决方案2】:

我正在考虑类似于 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 {...} 的简单调用 - 没有可能的竞争条件。

【讨论】:

  • 这不是一个直接的解决方案,而是一个非常优雅的解决方法。谢谢!
  • 请注意,在您提议的 API 中,可能会忘记unlock() - 或者在到达该行之前可能会引发异常。
【解决方案3】:

如果您为用作锁定的对象提供 getter,则可以使用标准锁定机制。 虽然我不认为这是一个优雅的解决方案,但它肯定会成功。

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

由于 C# 同步块是可重入的,您将能够调用列表的内部同步方法,例如示例中的 add()remove()

另一种解决方案是使用visitor pattern,并让访问者操作被同步块包围。

【讨论】:

  • 如果你能在C#中使用java同步块,我印象深刻。
  • @Damien_The_Unbeliever 感谢您指出这一点。不过,答案并没有太大变化。
【解决方案4】:

我看不出有什么理由不使用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;
        }
    }
}

【讨论】:

    猜你喜欢
    • 2012-05-30
    • 2013-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多