2008-10-05 19 views
1

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

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

我是否缺少任何东西,在概念上?这应该工作,对吧?

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

回答

3

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

但是,实际上大多数锁都需要超出这个范围,就像收集锁只是添加等的帮助一样 - 呼叫者通常需要一个锁来跨越“是吗?如果是这样更新,否则添加“序列。

对于像bool这样简单的东西来说,“volatile”可能会更简单地解决问题 - 特别是如果它只是用于循环退出。您可能还想考虑[MethodImpl(MethodImplOptions.Synchronized)] - 虽然我个人更喜欢私人锁定对象(就像您使用过的),以防止外部人员锁定对象时出现问题(上述使用“this”作为锁)。对于单元测试这个,你需要一些东西来证明它首先被破坏 - 这是很难的,因为操作太小(并且对于大多数数据类型已经是原子的)。它避免的其他事情之一(即易失性也可以修复)缓存在寄存器中,但这又是一次优化,很难证明它已被破坏。

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

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; 
+0

示例代码不会,除非锁定(实例)的工作被宣布其他地方重新用于所有呼叫者 - 否则每个呼叫者都有不同的锁定,这会破坏目的。然后它会更简单地声明一个对象并锁定(obj) – 2008-10-05 08:29:38