多线程增量和跳过 0 没有锁?
Posted
技术标签:
【中文标题】多线程增量和跳过 0 没有锁?【英文标题】:multi-threaded increment and skip 0 without a lock? 【发布时间】:2013-09-03 19:38:09 【问题描述】:我有一个超短计数器(偶尔会翻转)。使用此值的消息传递协议不允许 0。我需要一些线程安全的方法来每次读取此计数器(存储在类字段中)递增,如果我将其存储为 int 并使用互锁。增量。但是,我不确定如何将跳过 0 纳入其中。偶尔跳过几个数字也没关系;我的输出序列不一定是完美的。我不能在任何 4000 块中重复使用相同的数字。我想避免使用锁。
【问题讨论】:
你能用do result = Interlocked.Increment(ref value); while (result==0);
来避免0吗?
我认为只要在 while 检查中将结果转换为 ushort 就可以了。似乎在碰撞情况下它只会跳过 1.
@MarcGravell 其他一些线程可以读取Interlocked.Increment
s 之间的 0 值。
@xanatos 所以?没关系 - 因为新数字之后的所有代码都会运行相同的访问器 - 所以没有人会得到零
@MarcGravell 您可以让纯读者在阅读 0 并“伪造它”时返回 1……(他们不知道它是否已经是 1……但他们知道它将变成 1如果它是 0,则在几秒钟内)
【参考方案1】:
这个:
给定:
static int value = ushort.MaxValue;
在代码中:
int temp, temp2;
do
temp = value;
temp2 = temp == ushort.MaxValue ? 1 : temp + 1;
while (Interlocked.CompareExchange(ref value, temp2, temp) != temp);
您必须使用 int
然后对其进行转换(例如在 get
属性中),因为 Interlocked
并不适用于所有基本类型。
我们可能会在像这样的高度线程化的上下文中让它更快一点:
int temp = value;
while (true)
int temp2 = temp == ushort.MaxValue ? 1 : temp + 1;
int temp3 = Interlocked.CompareExchange(ref value, temp2, temp);
if (temp3 == temp)
break;
temp = temp3;
通过这种方式,我们必须少读一次失败。
正如我在评论中所写,这段代码的中心思想是在计数器中增加一个临时变量 (temp2
),然后尝试用新值 (@ 987654328@)。如果没有人触及中间的旧值(Interlocked.CompareExchange() == temp
),那么我们就完成了。如果其他人增加了该值,那么我们再试一次。 ushort
是通过使用具有固定最大值 (temp == ushort.MaxValue ? 1 : temp + 1
) 的 int
来模拟的。
第二个版本,在 Interlocked.CompareExchange()
失败时重用函数读取的值作为添加 1 的新基础。
以这种方式使用的Interlocked.CompareExchange
可以用作构建其他Interlocked
操作的基础(你想要Interlocked.Multiply
?你做一个“标准”乘法然后尝试Interlocked.CompareExchange
旧值)
【讨论】:
我仍然需要一个 ushort 结果。我不确定这是否检测到超短翻转。 这对我来说有点过分。我认为当从多个线程同时调用时这会中断。 @spender 可以使用int
作为支持字段。修改了代码。
@spender 第一次读取 (temp = value;
) 可能是脏的,但如果它脏了,Interlocked.CompareExchange
将失败并强制“真正”读取该值。然后重新读取该值,但此时我们与其他线程“同步”。如果两个线程同时进入该代码块,则只有一个Interlocked.CompareExchange
会成功,而另一个会失败。第二个版本对失败的读取减少了(我们重用了Interlocked.CompareExchange
读取)
+1:这绝对是正确的答案。对于那些不知道的人,最上面的例子是创建复杂操作的类似联锁变体的基本模板。我已经多次这样做并且效果很好。这是一个久经考验的模式。以上是关于多线程增量和跳过 0 没有锁?的主要内容,如果未能解决你的问题,请参考以下文章