1

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

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

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

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);
4

2 回答 2

3

仅在获取/设置期间锁定;当然,在许多常见情况下,这将是原子的,这仅仅是由于数据大小。

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

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

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

对于这个单元测试,你需要先证明它被破坏了——这很难,因为操作是如此之小(并且对于大多数数据类型来说已经是原子的)。它避免的另一件事(易失性也修复了)是缓存在寄存器中,但这又是一种优化,很难强制证明它被破坏了。

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

于 2008-10-05T08:06:29.360 回答
1

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

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

这里有一个非常明显的竞争条件。当Get()调用返回时,对象不再被锁定。要进行真正的 post 或 pre 递增,需要将计数器从 . 之前锁定GetSet.

此外,如果您所做的只是同步读取,您并不总是需要完全锁定。

更好的锁定接口(我认为)需要您显式锁定需要执行此操作的实例。我的经验主要是 C++,所以我不能推荐一个完整的实现,但我喜欢的语法可能看起来像这样:

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

// read value
print instance;
于 2008-10-05T08:11:05.853 回答