在MTA中使锁定更容易

时间:2008-10-05 07:44:19

标签: c# multithreading locking

在多线程代码中,当一个实例可能被多个线程读取或写入时,需要锁定它们以安全地执行这些操作。

为了避免重复创建要锁定的对象并通过代码编写一堆锁定语句,我创建了一个通用类来处理锁定。

概念上,我错过了什么吗?这应该有用,对吧?

public class Locked<T> where T : new()
{
    private readonly object locker = new object();

    private T value;

    public Locked()
        : this(default(T))
    { }

    public Locked(T value)
    {
        this.value = value;
    }

    public T Get()
    {
        lock (this.locker)
        {
            return this.value;
        }
    }

    public void Set(T value)
    {
        lock (this.locker)
        {
            this.value = value;
        }
    }    
}

在课堂上使用它的一个例子:

private Locked<bool> stopWorkerThread = new Locked<bool>();

public void WorkerThreadEntryPoint()
{
    while (true)
    {
        if (this.stopWorkerThread.Get())
        {
            break;

}

另外,我如何以自动方式测试这样的东西(例如创建一个单元测试)?

最后,我可以做些什么来实现++和 - 运算符,以避免这种情况:

        this.runningThreads.Set(this.runningThreads.Get() + 1);

2 个答案:

答案 0 :(得分:3)

只在get / set的持续时间内锁定;当然,在许多常见情况下,无论如何这都是原子的,仅仅是由于数据大小。

然而,实际上大多数锁需要跨越这个,就像锁定Add等的集合没有多大帮助一样 - 调用者通常需要一个锁来跨越“它在那里吗?”如果所以更新,否则添加“序列。

对于像bool这样简单的东西,“volatile”可能会更简单地解决问题 - 特别是如果它仅用于循环退出。

您可能还想考虑[MethodImpl(MethodImplOptions.Synchronized)] - 虽然我个人更喜欢私有锁对象(就像您已经使用过的)来防止外部人员锁定对象的问题(上面使用“this”作为锁)。

对于单元测试,你需要一些东西来证明它首先被破坏 - 这很难,因为操作太小了(对于大多数数据类型来说已经是原子的)。它避免的其他事情之一(易失性也修复)是在寄存器中缓存,但这又是一种优化,很难强制证明它已被破坏。

如果您对锁定包装器感兴趣,可以考虑现有代码,如this

答案 1 :(得分:1)

上面的代码有很多潜在的和真正的多线程问题,我不会在现实世界中使用类似的东西。例如:

this.runningThreads.Set(this.runningThreads.Get() + 1);

这里有一个非常明显的竞争条件。当Get()调用返回时,该对象不再被锁定。要进行实际发布或预增量,需要在Get之前锁定计数器Set之后。

如果你所做的只是同步读取,你也不总是需要完全锁定。

更好的锁定接口(我认为)要求您明确锁定需要执行的实例。我的经验主要是使用C ++,所以我不推荐完整的实现,但我的首选语法可能如下所示:

using (Locked<T> lock = Locked<T>(instance))
{
  // write value
  instance++;
}

// read value
print instance;