提问人:user22422035 提问时间:8/21/2023 最后编辑:providerZuser22422035 更新时间:8/22/2023 访问量:52
联锁类:先读后写争用条件问题
Interlocked Class: read before write race condition problem
问:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace InterlockedLearning
{
class Program
{
static int sharedVariable = 0;
static void Main()
{
Parallel.For(0, 1000000, Func1);
Console.WriteLine("Thread{0} sharedVariable: {1}", Thread.CurrentThread.ManagedThreadId, sharedVariable);
Console.Read();
}
public static void Func1(int val)
{
int i = Interlocked.CompareExchange(ref sharedVariable, 0, 0);
if (i < 500000)
{
Interlocked.Increment(ref sharedVariable);
}
}
}
我正在研究如何修改上述代码,以便不再发生争用条件问题。
上面的代码结果应该是 500000,但如果运行多次,代码的最终结果500001。我认为这可能是由检查条件引起的。
我知道它可以通过简单地使用锁来解决,但我想知道是否有任何非阻塞风格的方法可以解决这个问题。
答:
2赞
Guru Stron
8/21/2023
#1
我认为这可能是由检查条件引起的。
是的,没错,读取然后检查它不是原子操作 - 想象一下 2 个线程(A 和 B)做(为什么不只是)获取499999,执行检查然后递增值。例如:int i
int i = Interlocked.CompareExchange(ref sharedVariable, 0, 0);
Interlocked.Read
- 线程 A 读取 sharedVariable = 499999
- 线程 A 检查 sharedVariable < 500000 (true)
- 线程 B 读取 sharedVariable = 499999
- 线程 B 检查 sharedVariable < 500000 (true)
- 线程 A 递增 sharedVariable (500000)
- 线程 B 增量 sharedVariable (500001)
我知道它可以通过简单地使用锁来解决,但我想知道是否有任何非阻塞样式的方法来解决这个问题。
我能想到的不多,但例如,您可以在溢出的情况下减少:
public static void Func1(int val)
{
if (sharedVariable >= 500000) return;
if (Interlocked.Increment(ref sharedVariable) > 500000)
{
Interlocked.Decrement(ref sharedVariable);
}
}
另一种选择是循环使用(请参阅文档中的示例)。CompareExchange
附言
强烈建议您查看 Deadlock Empire,这将使理解此类情况变得更加容易。
评论
0赞
user22422035
8/21/2023
public static void Func1(int val) { if (Interlocked.Read(ref sharedVariable) < 500000) { Interlocked.Increment(ref sharedVariable);我改用 Interlocked.Read() 修改了代码,竞争条件问题再次发生
2赞
Guru Stron
8/21/2023
@user22422035 是的,因为它有所有相同的问题 - 其次是非原子的比较(2 个单独的操作)。这句话是关于你的用法,没有多大意义,因为它实际上只是你正在寻找的。Read
CompareExchange
Read
3赞
canton7
8/21/2023
#2
使用以下方法更新字段的一般模式是(来自 MSDN):CompareExchange
int initialValue, computedValue;
do {
// Save the current running total in a local variable.
initialValue = totalValue;
// Add the new value to the running total.
computedValue = initialValue + addend;
// CompareExchange compares totalValue to initialValue. If
// they are not equal, then another thread has updated the
// running total since this loop started. CompareExchange
// does not update totalValue. CompareExchange returns the
// contents of totalValue, which do not equal initialValue,
// so the loop executes again.
} while (initialValue != Interlocked.CompareExchange(ref totalValue, computedValue, initialValue));
根据你的方案进行调整:Adapting this to your scenario:
int initialValue, computedValue;
do
{
initialValue = sharedVariable;
if (initialValue >= 500000)
{
break;
}
computedValue = initialValue + 1;
} while (initialValue != Interlocked.CompareExchange(ref sharedVariable, computedValue, initialValue));
这说:
- 读取字段的当前值。
- 如果当前值为 >= 500000,则完成。
- 通过添加 1 来计算新值
- 如果该字段自我们读取以来未发生更改,请将其更新为新的计算值。如果自从我们读到它以来它发生了变化,那么有一场比赛,我们需要再试一次。
有可能有一场比赛,我们读取 ,然后其他人递增它,然后我们与 500000 进行比较,但这没关系。如果在其他人递增之前它是 >= 500000,那么在他们递增它之后,它也将是 >= 500000。sharedVariable
评论
1赞
Theodor Zoulias
8/21/2023
虽然这在技术上是正确的解决方案,但它的性能可能比简单的 .问题的作者不应该认为无锁等于更快。lock
2赞
canton7
8/21/2023
绝对。在寻找性能时始终以基准为基准
评论