线程同步.完全锁定如何使内存访问"正确"?

Rom*_*man 14 c# multithreading locking memory-barriers

首先,我知道这lock{}是合成糖Monitor.(哦,语法糖)

我正在玩简单的多线程问题,并发现无法完全理解锁定一些任意WORD的内存如何保护整个其他内存不被缓存的是寄存器/ CPU缓存等.使用代码示例来解释我所说的更容易:

for (int i = 0; i < 100 * 1000 * 1000; ++i) {
    ms_Sum += 1;
}
Run Code Online (Sandbox Code Playgroud)

最终ms_Sum将包含100000000哪些当然是预期的.

现在我们年龄将执行相同的周期,但在2个不同的线程上,上限减半.

for (int i = 0; i < 50 * 1000 * 1000; ++i) {
    ms_Sum += 1;
}
Run Code Online (Sandbox Code Playgroud)

由于没有同步,我们得到了不正确的结果 - 在我的4核机器上,它几乎是随机数52 388 219,略大于一半100 000 000.如果我们奉上ms_Sum += 1;lock {},我们,事业,会得到完全正确的结果100 000 000.但是,什么是对我感兴趣的(真正的说,我期待一样的行为),加入lock前,后ms_Sum += 1;线,使回答几乎是正确的:

for (int i = 0; i < 50 * 1000 * 1000; ++i) {
    lock (ms_Lock) {}; // Note curly brackets

    ms_Sum += 1;
}
Run Code Online (Sandbox Code Playgroud)

对于这种情况,我通常会得到ms_Sum = 99 999 920,这非常接近.

问题:为什么lock(ms_Lock) { ms_Counter += 1; }确实使程序完全正确但lock(ms_Lock) {}; ms_Counter += 1;只是几乎正确; 如何锁定任意ms_Lock变量使整个内存稳定?

非常感谢!

PS已经阅读有关多线程的书籍了.

类似问题(S)

lock语句如何确保内部处理器同步?

线程同步.为什么这个锁不足以同步线程

Eri*_*ert 15

为什么确实lock(ms_Lock) { ms_Counter += 1; }使程序完全正确但lock(ms_Lock) {}; ms_Counter += 1;只是几乎正确?

好问题!理解这一点的关键是锁做两件事:

  • 它会导致任何争用锁定的线程暂停,直到可以执行锁定
  • 它会造成记忆障碍,有时也称为"全围栏"

我不完全理解锁定一些任意对象如何阻止其他内存缓存在寄存器/ CPU缓存等中

正如您所知,在寄存器或CPU缓存中缓存内存可能会导致多线程代码中发生奇怪的事情.(请参阅我关于波动性的文章,以便对相关主题进行温和解释.)简要说明:如果一个线程另一个线程更改该内存之前在CPU缓存中复制了一页内存,那么第一个线程会从缓存,然后有效地第一个线程已经及时向后移动读取.同样,对内存的写入似乎可以及时向前移动.

内存屏障就像是一个及时的栅栏,它告诉CPU"做你需要做的事情,以确保随时间移动的读写操作不能越过栅栏".

一个有趣的实验是代替空锁,在那里调用Thread.MemoryBarrier(),看看会发生什么.你得到相同或不同的结果吗?如果你得到相同的结果,那么它就是帮助的内存障碍.如果你不这样做,那么线程几乎正确同步的事实就是减慢它们的速度以防止大多数比赛.

我的猜测是它是后者:空锁正在减慢线程,使得他们不会将大部分时间花在具有竞争条件的代码上.强内存模型处理器通常不需要内存屏障.(你是在x86机器上,还是Itanium,或者什么?x86机器有一个非常强大的内存模型,Itaniums有一个需要内存障碍的弱模型.)