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

我刚刚编写了一个由多个线程同时调用的方法,我需要跟踪所有线程何时完成。 代码使用这种模式:

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

这是代码中唯一一个_reportsRunning的值被更改的地方,该方法需要大约一秒钟才能运行。

偶尔当我有超过六个左右的线程一起运行报告时,_reportsRunning的最终结果可以降到-1。 如果我在锁中包含对_runningReports++_runningReports--的调用,那么该行为似乎是正确且一致的。

所以,问题是:当我在C ++中学习multithreading时,我被教导你不需要同步调用递增和递减操作,因为它们总是一个汇编指令,因此线程不可能在中间切换-呼叫。 我是否正确地教过,如果是这样,那对C#来说怎么回事?

A ++运算符在C#中不是primefaces的(我怀疑它在C ++中保证是primefaces的)所以是的,你的计数受竞争条件的影响。

使用Interlocked.Increment和.Decrement

 System.Threading.Interlocked.Increment(ref _reportsRunning); try { ... } finally { System.Threading.Interlocked.Decrement(ref _reportsRunning); } 

所以,问题是:当我在C ++中学习multithreading时,我被教导你不需要同步调用递增和递减操作,因为它们总是一个汇编指令,因此线程不可能在中间切换-呼叫。 我是否正确地教过,如果是这样的话,对C#来说怎么回事?

这是非常错误的。

在某些体系结构上,如x86,有单个递增和递减指令。 许多架构没有它们,需要单独加载和存储。 即使在x86上,也无法保证编译器会生成这些指令的内存版本 – 它可能首先加载到寄存器中,特别是如果它需要对结果执行多个操作。

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

请考虑以下x86汇编指令:

 inc [i] 

如果我最初为0并且代码在两个核心上的两个线程上运行,则两个线程完成后的值可以合法地为1或2,因为无法保证一个线程将在另一个线程完成其写入之前完成其读取,或者在其他线程读取之前,一个线程的写入甚至可见。

将此更改为:

 lock inc [i] 

将导致最终值为2。

Win32的InterlockedIncrementInterlockedDecrement以及.NET的Interlocked.IncrementInterlocked.Decrement导致执行lock inc的等效(可能完全相同的机器代码)。

你被教导错了。

确实存在具有primefaces整数增量的硬件,因此您所教授的内容可能适用于您当时使用的硬件和编译器。 但是一般来说,在C ++中你甚至不能保证增量非易失性变量在读取内存时连续写入内存,更不用说读取primefaces上的内存了。

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

这就是i++有效地做的事情:

  1. i加载到寄存器中
    • 递增寄存器
    • 将寄存器卸载到i中

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

您应该使用Interlocked.Increment和Interlocked.Decrement来解决这个问题。

不,您需要同步访问权限。 在Windows上,您可以使用InterlockedIncrement()和InterlockedDecrement()轻松完成此操作。 我确信其他平台也有等价物。

编辑:刚刚注意到了C#标签。 做其他人说的话。 另请参阅: 我听说i ++不是线程安全的,++ i是线程安全的吗?

更高级语言中的任何类型的递增/递减操作(是的,甚至C与机器指令相比更高级别)本质上不是primefaces的。 但是,每个处理器平台通常都具有支持各种primefaces操作的基元 。

如果您的讲师指的是机器指令,则递增和递减操作可能是primefaces的。 然而,在当今不断增加的多核平台上,这并不总是正确的,除非它们保证一致性 。

较高级语言通常使用低级primefaces机指令实现对primefacestransactions支持 。 这是由更高级API提供的互锁机制。

x ++可能不是primefaces的,但是++ x可能是(不确定的,但如果你考虑后增量和预增量之间的差异,那么应该很清楚为什么pre-更适合primefaces性)。

更重要的一点是,如果这些运行花费一秒钟来运行每个运行,那么锁定所添加的时间量将与方法​​本身的运行时间相比是噪声。 在这种情况下尝试移除锁定可能不值得一试 – 你有一个正确的锁定解决方案,这可能与非锁定解决方案的性能没有明显区别。

单处理器机器上,如果没有使用虚拟内存,x ++(rvalue ignored)可能会转换为x86体系结构上的单个primefacesINC指令(如果x很长,则使用32-时操作只是primefaces操作位编译器)。 此外,movsb / movsw / movsl是移动字节/字/长字的primefaces方式; 编译器不喜欢将它们用作分配变量的常规方法,但可以使用primefaces移动实用程序函数。 虚拟内存管理器有可能以这样的方式编写,即如果在写入时发生页面错误,那些指令将primefaces地运行,但我认为通常不会保证。

在多处理器机器上,除非使用显式互锁指令(通过特殊库调用可调用),否则所有投注都将关闭。 通用的最通用的指令是CompareExchange。 该指令只有在包含预期值时才会改变内存位置; 当它决定是否改变它时,它将返回它所具有的值。 如果有人希望用1“变量”变量,可以做一些像(在vb.net中)

   Dim OldValue as Integer
  做
     OldValue =变量
  而Threading.Interlocked.CompareExchange(Variable,OldValue Xor 1,OldValue)OldValue

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

重要提示:(1)保持环路尽可能短; 循环时间越长,另一个任务在循环期间命中变量的可能性就越大,每次发生时浪费的时间就越多; (2)在线程之间任意划分的指定数量的更新将始终完成,因为线程可以强制重新执行循环的唯一方法是,如果某个其他线程已经取得了有用的进展; 但是,如果某些线程可以在不向前完成的情况下执行更新,则代码可能会变为实时锁定。