什么是 C# 中用于发出唯一计数器值的简单线程安全赋值模式
本文关键字:简单 单线程 安全 模式 赋值 计数器 唯一 用于 什么 | 更新日期: 2023-09-27 17:57:09
我不是 C# 多线程专家;我想确保防止竞争条件,这种竞争条件在测试中很难触发,同时也几乎不可能调试。 要求(我的应用程序是一个实用程序类,用于可能的多线程 HTTP 服务器,而不是使用 IIS 或 ASP.NET)是类的每个实例都具有该实例的唯一标识符。
我宁愿不使用重量级 GUID 来避免此问题,因为它们的序列化长度,就像在 HTML 表示形式中一样。
我的问题是:在下面的Widget
类中设置Unique
值的简单模式是否适合此要求? 有没有更好、更安全的模式,希望实施起来不会造成负担?
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;
}
}
}
从这个答案:
如果希望以保证不受争用条件约束DoneCounter = DoneCounter + 1
的方式实现属性,则无法在属性的实现中执行此操作。 该操作不是原子操作,它实际上是三个不同的步骤:
- 检索
DoneCounter
的值。 - 添加 1
- 将结果存储在
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); }
如前所述,+=
(和相关)操作不是线程安全的。在进行简单的数字增量时,只需使用 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);
}
}