C#多线程无符号增量
我想从多个线程中增加一个无符号整数 我知道Interlocked.Increment,但它不处理无符号整数。我可以使用lock(),但出于性能原因,如果可能的话,我宁愿不使用C#多线程无符号增量,c#,multithreading,unsigned,interlocked-increment,C#,Multithreading,Unsigned,Interlocked Increment,我想从多个线程中增加一个无符号整数 我知道Interlocked.Increment,但它不处理无符号整数。我可以使用lock(),但出于性能原因,如果可能的话,我宁愿不使用 仅仅以正常方式增加它是线程安全的吗?偶尔的增量丢失了也没关系,因为它只用于统计。我不希望值损坏。您可以将uint声明为volatile 如果您真的需要完整范围的无符号整数(2^32-1)而不是有符号整数(2^31-1),您可以强制转换为int64(有一个互锁的.Increment重载,它接受int64)然后返回到一个无符号
仅仅以正常方式增加它是线程安全的吗?偶尔的增量丢失了也没关系,因为它只用于统计。我不希望值损坏。您可以将uint声明为volatile
如果您真的需要完整范围的无符号整数(2^32-1)而不是有符号整数(2^31-1),您可以强制转换为int64(有一个
互锁的.Increment
重载,它接受int64)然后返回到一个无符号整数。您说出于性能原因不想使用lock
,但是您已经测试过了吗?一个无争用的锁(听起来很可能是这样)相当便宜
在线程方面,我通常选择“显然是正确的”,而不是“聪明而且可能表现得更好”(一般来说,但特别是线程)
在锁定和不锁定的情况下对你的应用程序进行基准测试,看看你是否能注意到其中的差异。如果锁定有很大的不同,那么一定要使用狡猾的东西。否则,我就只能用锁了
您可能想做的一件事是使用Interlocked.Increment
和int
并在需要时将其强制转换为uint
,如下所示:
using System;
using System.Reflection;
using System.Threading;
public class Test
{
private static int count = int.MaxValue-1;
public static uint IncrementCount()
{
int newValue = Interlocked.Increment(ref count);
return unchecked((uint) newValue);
}
public static void Main()
{
Console.WriteLine(IncrementCount());
Console.WriteLine(IncrementCount());
Console.WriteLine(IncrementCount());
}
}
输出:
2147483647
2147483648
2147483649
(换句话说,它没有问题。)在使用有符号整数的两个补码表示的系统上(,根据维基百科),增加无符号整数与增加使用相同位集表示的有符号整数具有相同的效果。因此,可以在不牺牲任何东西的情况下对无符号整数使用InterlockedIncrement 例如,对于3位,我们有下表:
raw bits | unsigned integer | twos complement signed integer
------------------------------------------------------------
000 | 0 | 0
001 | 1 | 1
010 | 2 | 2
011 | 3 | 3
100 | 4 | -4
101 | 5 | -3
110 | 6 | -2
111 | 7 | -1
在这两种情况下,递增1(并考虑溢出)相当于在表中向下移动一个条目。请注意,这不适用于一补运算,因为负数按相反的顺序排列。在此基础上,您可以创建自己的助手类。由于增量在二进制级别上的工作方式相同,因此在使用Unsafe
类进行增量之前,您可以将类型从unsigned更改为signed:
使用System.Runtime.CompilerServices;
使用系统线程;
公共静态类Interlocatex
{
///
///的无符号等价物
///
公共静态单元增量(参考单元位置)
{
int incrementedSigned=联锁的增量(参考不安全的As(参考位置));
返回不安全的.As(参考增量签名);
}
///
///的无符号等价物
///
公共静态ulong增量(参考ulong位置)
{
long incrementedSigned=联锁增量(参考不安全的As(参考位置));
返回不安全的.As(参考增量签名);
}
}
自.NET 5.0以来,无符号和有符号的int32
和int64
都有互锁的.Increment
重载
请注意,尽管Interlocked.Increment(ref long)实际上仅在64位操作系统下是原子的(请参阅文档),但出于一般安全考虑,我个人愿意使用常规int。。。如果31位还不够,那么我怀疑32位是否真的能更好地工作,除非问题明确限制在2G和4G测量之间。@Jerryjvl-让我想起了在8位系统中16位计算的日子。事实上,没有范围缩小,正如我在回答中所述。@jerryjvl在about
Interlocked.Increment(Int64)
中没有提到在32位操作系统上不是原子的。这只能确保立即将变量的任何更改写回内存,而不是等待线程产生或寄存器中的值被替换。它不保护对字段的访问,即两个(或更多)线程可以读取相同的值,增加一次,然后写回,有效地将值增加1。请阅读上面的要求以及中的示例。对于给定的情况,这就足够了。与联锁增量相比,性能的波动性如何?同样的?更好?@jrista:也许不是最权威的基准测试,但在我的机器上,在8个CPU内核上启动8个任务,使用线程在紧循环中递增计数器。循环中的睡眠(0)
导致volatile
与interlocated.Increment具有相同的性能。使用lock
的速度慢了好几倍。这个答案在我看来是错误的<代码>易失性
在分配时有效。但不是为了递增。递增需要两个操作volatile
不会阻止其他线程在这两个操作之间插入。为了安全起见,您要么需要一个原子增量(fetch和add),就像Interlocked.increment()一样,要么需要将访问与锁定同步。我同意为什么要使事情复杂化,除非性能真正成为问题。我无法想象增加单个值的锁会保持很长时间或使用很多资源。强制转换(危险地)不是假设了一个“未检查”的上下文吗?我们想将2的补码符号int的位重新解释为uint,而不进行范围检查。为此,我们不应该显式地执行返回unchecked((uint)newValue)代码>?顺便说一句,我看不到在C#spec中有任何明确提到2的补码,但我想我们可以依赖它。@MaxBarraclough:是的,最好是明确取消选中。我们将做出改变。是的,你可以依赖2的补码,尽管它现在不在规范中。我们希望在以后的版本中包含它:)“因此,可以在不牺牲任何东西的情况下对无符号整数使用InterlockedIncrement。”-不,您不能,因为Interlocked.Increment()接受Int32或Int64