【问题标题】:Are basic arithmetic operations in C# atomicC#中的基本算术运算是原子的吗
【发布时间】:2023-12-28 00:45:01
【问题描述】:

基本算术运算线程安全吗?

比如对一个全局变量有++操作,会被不同的线程修改,是否需要加锁?

例如

void MyThread() // can have many running instances
{
    aGlobal++;
}

或者应该是

void MyThread()
{
    lock( lockerObj)
    {
        aGlobal++;
    }
}

【问题讨论】:

  • 这不是一个排他的算术运算......也有一个变量写入! a + aa = a + a 不同。你必须使用Interlocked.Increment 或类似的!

标签: c# thread-safety locking arithmetic-expressions


【解决方案1】:

The spec 总结得很好。第 5.5 节,“变量引用的原子性”:

以下数据类型的读写是原子的:bool、char、 byte、sbyte、short、ushort、uint、int、float 和引用类型。在 此外,读取和写入具有基础类型的枚举类型 前面的列表也是原子的。其他类型的读写, 包括long、ulong、double和decimal,以及用户自定义 类型,不保证是原子的。除了图书馆 为此目的而设计的功能,不能保证原子性 read-modify-write,比如递增或递减的情况。

结论:

  • 独立读取/写入是原子的(但仅适用于某些数据类型)
  • 读/修改/写(例如i++)是从不原子的
  • 您可以使用Interlocked 类方法在原子性尚未得到保证时实现原子性

Interlocked 功能不够的情况下,除了使用同步原语之外别无选择,例如Monitor.Enter(编译器也通过lock 语句公开)。

【讨论】:

    【解决方案2】:

    读取和写入独立在大多数类型(不是较长的类型,通常是 64 位以上)上都是原子的。但是您想要的是原子地读取、更改然后编写 - 这绝对是不是原子的。

    如果您需要增加一个值,可以使用带有静态 IncrementDecrement 操作的 System.Threading.Interlocked 类。

    如果您需要进行更复杂的求和,那么使用lock 或其他构造进行同步是唯一的方法。或者使事物像在消息传递系统中一样不可变,这样您就不会遇到任何共享数据访问问题,但这通常是无法实现的,除非预先设计。

    【讨论】:

      【解决方案3】:

      两者都不...您应该改用System.Threading.Interlocked.Increment(ref int location)。这是无锁的,因为它确保处理器以完全想要的顺序执行读取和写入(指令 - 是原子的) - 而不使用 System.Threading.Interlocked.Increment 为处理器提供了重新排序指令的机会。

      做大锤(或没有其他可能,因为您可能正在执行大量操作),您也可以使用lock - 但即使在这些情况下,我也更愿意使用System.Threading.ReaderWriterLockSlim

      顺便说一句 - a nice read!

      【讨论】:

        最近更新 更多