C#中用于发布唯一计数器值的简单线程安全分配模式是什么?

时间:2013-02-01 20:28:36

标签: c# thread-safety

我不是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;
        }
    }  

}

2 个答案:

答案 0 :(得分:2)

来自this answer

如果您希望以DoneCounter = DoneCounter + 1保证不受竞争条件限制的方式实施该属性,则无法在该属性的实现中完成。该操作不是原子操作,实际上是三个不同的步骤:

  1. 检索DoneCounter
  2. 的值
  3. 添加1
  4. 将结果存储在DoneCounter
  5. 您必须防止在任何这些步骤之间发生上下文切换的可能性。锁定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);
    }  
}