Val*_*zub 6 .net c# multithreading locking
可能重复:
线程同步.完全锁定如何使内存访问"正确"?
我们有一个以下的测试课程
class Test
{
private static object ms_Lock=new object();
private static int ms_Sum = 0;
public static void Main ()
{
Parallel.Invoke(HalfJob, HalfJob);
Console.WriteLine(ms_Sum);
Console.ReadLine();
}
private static void HalfJob()
{
for (int i = 0; i < 50000000; i++) {
lock(ms_Lock) { }// empty lock
ms_Sum += 1;
}
}
}
Run Code Online (Sandbox Code Playgroud)
实际结果非常接近预期值100 000 000(50 000 000 x 2,因为2个循环同时运行),差异大约为600 - 200(我的机器上的误差约为0.0004%,非常低).没有其他同步方式可以提供这种近似方式(它要么是更大的错误%,要么是100%正确)
我们目前理解这种精确程度是因为程序以下列方式运行:

时间从左到右运行,2个线程由两行表示.
哪里
黑匣子代表获取,持有和释放的过程
lock plus表示加法操作(架构表示我的PC上的比例,锁定大约比添加的时间长20倍)
锁也提供完整的内存栅栏.
所以现在的问题是:如果上面的模式代表了正在发生的事情,那么这个大错误的原因是什么(现在它的大原因模式看起来非常强大的同步模式)?我们可以理解1-10之间的界限差异,但它显然不是错误的唯一原因吗?我们无法看到何时可以同时发生对ms_Sum的写入,从而导致错误.
编辑:很多人喜欢快速得出结论.我知道同步是什么,如果我们需要正确的结果,那么上面的构造不是真正的或接近好的同步线程的方法.对海报有一些信心,或者先阅读相关的答案.我不需要同步2个线程来并行执行添加的方法,我正在探索这种奢侈而又高效的方法,与任何可能的和近似的替代方案相比,同步构造(它在某种程度上同步,因此它不像建议的那样毫无意义)
lock(ms_Lock) { }这是毫无意义的构造.lock保证在其中独占执行代码.
让我解释为什么这个空的lock减少(但不会消除!)数据损坏的可能性.让我们简化一下线程模型:
Nops 填充代码表示前一行仍在执行.)+=需要3个步骤.我将它们分解为原子的.在左列显示哪条线在线程的时间片(A和B)处执行.在右栏 - 程序(根据我的模型).
A B
1 1 SomeOperation();
1 2 SomeOperation();
2 3 Monitor.Enter(ms_Lock);
2 4 Nop();
3 5 Nop();
4 6 Monitor.Exit(ms_Lock);
5 7 Nop();
7 8 Nop();
8 9 int temp = ms_Sum;
3 10 temp++;
9 11 ms_Sum = temp;
4
10
5
11
A B
1 1 SomeOperation();
1 2 SomeOperation();
2 3 int temp = ms_Sum;
2 4 temp++;
3 5 ms_Sum = temp;
3
4
4
5
5
Run Code Online (Sandbox Code Playgroud)
正如您在第一个场景中看到的那样,线程B无法捕获线程A而A有足够的时间来完成执行ms_Sum += 1;.在第二种情况下,ms_Sum += 1;交错并导致持续的数据损坏.实际上,线程调度是随机的,但这意味着线程A 在另一个线程到达之前有更多的更改来完成增量.
这是一个非常紧凑的循环,内部没有多少进展,因此ms_Sum += 1有可能被并行线程在"错误的时刻"执行.
你为什么要在实践中写这样的代码?
为什么不:
lock(ms_Lock) {
ms_Sum += 1;
}
Run Code Online (Sandbox Code Playgroud)
要不就:
Interlocked.Increment(ms_Sum);
Run Code Online (Sandbox Code Playgroud)
?
- 编辑---
一些评论为什么你会看到错误尽管锁的内存屏障方面...想象一下以下场景:
lock,离开lock,然后被OS调度程序抢占.lock(可能一次,可能多于一次,可能数百万次).ms_Sum += 1,导致一些增量丢失(因为增量=加载+添加+存储).