1

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

}
4

2 回答 2

2

这个答案

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

  1. 检索 的值DoneCounter
  2. 添加 1
  3. 将结果存储在DoneCounter.

您必须防止在任何这些步骤之间发生上下文切换的可能性。锁定在 getter 或 setter 内无济于事,因为锁定范围完全存在于其中一个步骤(1 或 2)中。如果您想确保所有三个步骤同时发生而不会被中断,那么您的同步必须涵盖所有三个步骤。这意味着它必须发生在包含所有这三个的上下文中。这可能最终会成为不属于包含该DoneCounter属性的任何类的代码。

使用您的对象的人有责任注意线程安全。一般来说,任何具有读/写字段或属性的类都不能以这种方式成为“线程安全的”。但是,如果您可以更改类的接口以便不需要设置器,则可以使其更加线程安全。例如,如果您知道 DoneCounter 只会递增和递减,那么您可以像这样重新实现它:

private int _doneCounter;
public int DoneCounter { get { return _doneCounter; } }
public void IncrementDoneCounter() { Interlocked.Increment(ref _doneCounter); }
public void DecrementDoneCounter() { Interlocked.Decrement(ref _doneCounter); }
于 2013-02-01T20:31:04.933 回答
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);
    }  
}
于 2013-02-01T20:37:52.877 回答