代码之家  ›  专栏  ›  技术社区  ›  Martin Harris

我需要将线程访问同步到int吗?

  •  20
  • Martin Harris  · 技术社区  · 17 年前

    private void RunReport()
    {
       _reportsRunning++;
    
       try
       {
           //code to run the report
       }
       finally
       {
           _reportsRunning--;
       }
    }
    

    _reportsRunning 的值发生了变化,该方法大约需要一秒钟的时间才能运行。

    _runningReports++ 和 _runningReports-- 在锁中,行为似乎是正确和一致的。

    所以,对于这个问题:当我在C++中学习多线程时,我被教导说,你不需要将调用同步到递增和递减操作,因为它们总是一条汇编指令,因此在调用过程中不可能将线程切换出去。我被正确地教过吗?如果是这样,为什么这不适用于C#?

    8 回复  |  直到 17 年前
        1
  •  27
  •   Henk Holterman    12 年前

    A. ++ 运算符在C#中不是原子性的(我怀疑它在C++中一定是原子性的),所以是的,你的计数受竞争条件的约束。

    使用联锁。增量和。递减

    System.Threading.Interlocked.Increment(ref _reportsRunning);
    try 
    {
      ...
    }
    finally
    {
       System.Threading.Interlocked.Decrement(ref _reportsRunning);
    }
    
        2
  •  21
  •   Peter Mortensen Pieter Jan Bonestroo    16 年前

    教你不需要 将呼叫同步到增量和 递减操作,因为它们 始终只有一份装配说明 我被正确地教导了吗?如果是这样,是怎么教的 为什么这不适用于C#?

    这是非常错误的。

    即使编译器可以保证在x86上始终生成递增和递减的内存版本,这仍然不能保证原子性——两个CPU可能会同时修改变量并得到不一致的结果。该指令需要锁前缀来强制它是一个原子操作——默认情况下,编译器永远不会发出锁变体,因为它保证了操作是原子的,因此性能较低。

    考虑以下x86汇编指令:

    inc [i]
    

    如果I最初为0,并且代码在两个内核上的两个线程上运行,那么两个线程完成后的值在法律上可以是1或2,因为不能保证一个线程在另一个线程完成写入之前完成读取,或者一个线程的写入甚至在其他线程读取之前可见。

    lock inc [i]
    

    InterlockedIncrement 和 InterlockedDecrement Interlocked.Increment 和 Interlocked.Decrement lock inc .

        3
  •  4
  •   Steve Jessop    17 年前

    你被教错了。

        4
  •  3
  •   Peter Mortensen Pieter Jan Bonestroo    16 年前

    递增 int 这是一条指令,但在寄存器中加载值呢?

    i++

    1. 我 进入登记册
      • 递增寄存器
      • 我

    如您所见,有3条指令(在其他平台上可能有所不同),在任何阶段,cpu都可以上下文切换到不同的线程,使变量处于未知状态。

    你应该使用 Interlocked.Increment 和 Interlocked.Decrement

        5
  •  2
  •   Community Mohan Dere    9 年前

    不,您需要同步访问。在Windows上,您可以使用Interlocked增量()和Interlocked递减()轻松完成此操作。我相信其他平台也有类似的东西。

    I've heard i++ isn't thread safe, is ++i thread-safe?

        6
  •  1
  •   nik    17 年前

    高级语言中的任何类型的递增/递减操作(是的,甚至 C 与机器指令相比是更高级别的)本质上不是原子的。然而,每个处理器平台通常都有 primitives that support various atomic operations .

    如果你的讲师指的是机器指令,增量和减量操作可能是原子操作。然而,在当今不断增长的多核平台上,这并不总是正确的,除非他们保证 coherency .

    support for atomic transactions

        7
  •  0
  •   kyoryu    17 年前

        8
  •  0
  •   supercat    16 年前

    在a 单处理机 机器,如果不使用虚拟内存,x++(右值忽略)很可能会在x86架构上转换为单个原子INC指令(如果x很长,则使用32位编译器时,该操作仅为原子)。此外,movsb/movsw/movsl是移动字节/字/长字的原子方式;编译器不倾向于使用这些作为分配变量的正常方式,但可以使用原子移动实用程序函数。如果在写入过程中发生页面错误,虚拟内存管理器的编写方式可能会使这些指令的行为原子化,但我认为这通常不能保证。

      Dim OldValue as Integer
      Do
        OldValue = Variable
      While Threading.Interlocked.CompareExchange(Variable, OldValue Xor 1, OldValue)  OldValue
    

    这种方法允许对新值应依赖于旧值的变量执行任何类型的原子更新。对于某些常见的操作,如递增和递减,有更快的替代方案,但CompareExchange也允许实现其他有用的模式。

    重要注意事项:(1)保持循环尽可能短;循环时间越长,另一个任务在循环过程中遇到变量的可能性就越大,每次发生这种情况都会浪费更多的时间;(2)在线程之间任意分配的指定数量的更新将始终完成,因为强制一个线程重新执行循环的唯一方法是如果其他线程取得了有益的进展;但是,如果某些线程可以执行更新而不向前推进完成,则代码可能会被实时锁定。