在多线程代码中,当一个实例可能被多个线程读取或写入时,需要锁定它们以安全地执行这些操作。
为了避免重复创建要锁定的对象并通过代码编写一堆锁定语句,我创建了一个通用类来处理锁定。
概念上,我错过了什么吗?这应该有用,对吧?
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);
答案 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;