gpt4 book ai didi

c# - 使用静态变量的并发

转载 作者:太空狗 更新时间:2023-10-29 22:07:21 26 4
gpt4 key购买 nike

在下面的代码中,如果 GetNextNumber() 被两个线程同时调用,是否有可能向两个线程返回相同的数字?

class Counter
{
private static int s_Number = 0;
public static int GetNextNumber()
{
s_Number++;
return s_Number;
}
}

你能解释一下为什么吗?

编辑:如果代码有可能向两个线程返回相同的数字,那么以下是否正确?假设当 s_Number 等于 2 时,两个线程调用 GetNextNumber()。如果返回相同的值,则该值只能是 4。它不能是 3。是对吗?

最佳答案

处理这种简单的计数器时,最好使用Interlocked.Increment :

private static int s_Number = 0;

public static int GetNextNumber()
{
return Interlocked.Increment(ref s_Number);
}

这将确保每个线程都将返回一个唯一值(只要数字不溢出),并且不会丢失任何增量。

由于原始代码可以分解为以下步骤:

  1. 读取s_Number的现有值
  2. 加1
  3. 将新值存入s_Number
  4. 读取s_Number
  5. 返回读取的值

可能发生的场景是:

  1. 两个线程在其余部分之前执行步骤 1,这意味着两个线程将读取相同的现有值,递增 1,最终得到相同的值。 丢失了增量
  2. 线程可以执行第 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;
}

关于c# - 使用静态变量的并发,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15966903/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com