我不是C#中多线程的专家;我想确保防止竞争条件,这在测试中肯定难以触发,同时几乎无法调试。要求(我的应用程序是在可能的多线程HTTP服务器中使用的实用程序类,而不是使用IIS或ASP.NET)是每个类的实例都具有该实例的唯一标识符。
我宁愿不使用重量级的GUID来避免这个问题,因为它们的序列化长度,就像在HTML表示中一样。
我的问题是:在Unique
类中设置Widget
值的简单模式是否符合此要求的良好模式?是否有更好,更安全的模式,希望不会繁琐实施?
public abstract class Widget : Node
{
private static int UniqueCount = 0;
private static object _lock = new object();
protected int Unique { private set; get; }
protected Widget() : base()
{
// There could be a subtle race condition if this is not thread-safe
// if it is used with a multi-threaded web server
lock (_lock)
{
UniqueCount += 1;
Unique = UniqueCount;
}
}
}
答案 0 :(得分:2)
来自this answer:
如果您希望以DoneCounter = DoneCounter + 1
保证不受竞争条件限制的方式实施该属性,则无法在该属性的实现中完成。该操作不是原子操作,实际上是三个不同的步骤:
DoneCounter
。DoneCounter
。您必须防止在任何这些步骤之间发生上下文切换的可能性。锁定getter或setter内部将无济于事,因为锁定范围完全存在于其中一个步骤(1或2)中。如果要确保所有三个步骤一起发生而不被中断,那么您的同步必须涵盖所有三个步骤。这意味着它必须在包含所有这三个的上下文中发生。这可能最终会成为不属于包含DoneCounter
属性的任何类的代码。
使用您的物体的人有责任照顾线程安全。通常,没有具有读/写字段或属性的类可以以这种方式“线程安全”。但是,如果您可以更改类的接口以便不需要setter,则可以使其更加线程安全。例如,如果您知道DoneCounter只增加和减少,那么您可以像这样重新实现它:
private int _doneCounter;
public int DoneCounter { get { return _doneCounter; } }
public void IncrementDoneCounter() { Interlocked.Increment(ref _doneCounter); }
public void DecrementDoneCounter() { Interlocked.Decrement(ref _doneCounter); }
答案 1 :(得分:1)
如上所述,+=
(及相关)操作不是线程安全的。在进行简单的数字增量时,只需使用Interlocked.Increment
;它将返回旧值并且完全是线程安全的:
public abstract class Widget : Node
{
private static int UniqueCount = 0;
protected int Unique { private set; get; }
protected Widget() : base()
{
Unique = Interlocked.Increment(ref UniqueCount);
}
}