在处理这样一个简单的计数器时,最好使用Interlocked.Increment
:
private static int s_Number = 0;
public static int GetNextNumber()
{
return Interlocked.Increment(ref s_Number);
}
这将确保每个线程将返回一个唯一值(只要数字不溢出),并且不会丢失任何增量。
由于原始代码可以分解为以下步骤:
- 读取现有值
s_Number
- 加1
- 将新值存储到
s_Number
- 读
s_Number
- 返回读取值
可能发生的场景有:
- 两个线程在其余线程之前执行第 1 步,这意味着两个线程将读取相同的现有值,递增 1,最终得到相同的值。失去一个增量
- 线程可以在没有冲突的情况下执行步骤 1 到 3,但在两个线程都更新了变量并检索到相同的值之后最终执行步骤 4。跳过了一个数字
对于需要原子访问更多数据的较大代码段,lock
语句通常是更好的方法:
private readonly object _SomeLock = new object();
...
lock (_SomeLock)
{
// only 1 thread allowed in here at any one time
// manipulate the data structures here
}
但是对于这样一段简单的代码,您需要做的就是原子地增加一个字段并检索新值,Interlocked.Increment
这是更好、更快、更少的代码。
类中还有其他方法Interlocked
,它们在处理的场景中非常方便。
更详细的解释丢了一个增量。
让我们假设s_Number
在两个线程执行之前从 0 开始:
Thread 1 Thread 2
Read s_Number = 0
Read s_Number = 0
Add 1 to s_Number, getting 1
Add 1 to s_Number, getting 1 (same as thread 1)
Store into s_Number (now 1)
Store into s_Number (now 1)
Read s_Number = 1
Read s_Number = 1
Return read value (1)
Return read value (1)
正如您在上面看到的,最终值s_Number
应该是 2,其中一个线程应该返回 1,另一个应该返回 2。相反,最终值是 1,两个线程都返回 1。您在这里丢失了一个增量。
跳号详解
Thread 1 Thread 2
Read s_Number = 0
Add 1 to s_Number, getting 1
Store into s_Number (now 1)
Read s_Number = 1
Add 1 to s_Number, getting 2
Store into s_Number (now 2)
Read s_Number = 2
Read s_Number = 2
Return read value (2)
Return read value (2)
这里的最终结果s_Number
将是 2,这是正确的,但是其中一个线程应该返回 1,而不是它们都返回了 2。
让我们看看原始代码在 IL 级别上的样子。我会将原始代码添加到带有注释的 IL 指令中
// public static int GetNumber()
// {
GetNumber:
// s_Number++;
IL_0000: ldsfld UserQuery.s_Number // step 1: Read s_Number
IL_0005: ldc.i4.1 // step 2: Add 1 to it
IL_0006: add // (part of step 2)
IL_0007: stsfld UserQuery.s_Number // step 3: Store into s_Number
// return s_Number;
IL_000C: ldsfld UserQuery.s_Number // step 4: Read s_Number
IL_0011: ret // step 5: Return the read value
// }
注意,我使用LINQPad来获取上面的 IL 代码,启用优化(右下角的小 /o+),如果你想玩代码看看它是如何转换成 IL 的,请下载 LINQPad 并将其提供给这个程序:
void Main() { } // Necessary for LINQPad/Compiler to be happy
private static int s_Number = 0;
public static int GetNumber()
{
s_Number++;
return s_Number;
}