线程同步。究竟如何锁定使得对内存的访问“正确”? [英] Threads synchronization. How exactly lock makes access to memory 'correct'?

查看:142
本文介绍了线程同步。究竟如何锁定使得对内存的访问“正确”?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,我知道锁定{} 显示器类合成糖。 (哦,语法糖)



我是用简单的多线程问题玩耍,发现无法完全理解的内存lockng一些任意WORD如何保证整个被缓存等内存寄存器/ CPU缓存等,这是更容易使用代码示例来解释我在说什么:

 的for(int i = 0; I< 100 * 1000 * 1000; ++ I){
ms_Sum + = 1;
}

在结束 ms_Sum 将包含亿这是当然的预期。



现在,我们的年龄要执行相同的周期,但是, 2不同的线程,并减半上限

 的for(int i = 0; I< 50 * 1000 * 1000 + + I){
ms_Sum + = 1;
}

由于没有同步得到不正确的结果 - 我的4芯机就可以了是随机数接近 52 388 219 比一半稍大100 000 000 。如果我们附上 ms_Sum + = 1; 锁定{} ,我们,事业,会得到完全正确的结果 100 000 000 。但是,什么是对我感兴趣的(真正的说我的预期的一致好评行为),添加锁定之前在 ms_Sum + = 1 ; 行让答案的几乎正确的:

 的for(int i = 0; I< 50 * 1000 * 1000; ++ I){
锁(ms_Lock){}; //注意大括号

ms_Sum + = 1;
}

有关这种情况下,我通常会 ms_Sum = 99 999 920 ,这是非常接近



问:到底为什么锁(ms_Lock) {ms_Counter + = 1; } 使得程序完全正确的,但锁(ms_Lock){}; ms_Counter + = 1; 唯一的几乎的正确;如何锁定任意 ms_Lock 变量品牌的整个存储稳定?



非常感谢!



PS去了解多线程的书籍。



类似的问题(S)



如何lock语句保证处理器内部同步<? / A>



线程同步。到底为什么这个锁是不够的同步线程


解决方案

为什么究竟锁(ms_Lock){ms_Counter + = 1; } 使程序完全正确的,但锁(ms_Lock){}; ms_Counter + = 1; 只差一点正确




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




  • 这会导致争奇斗艳锁暂停,直到锁可以是任何线程采取

  • 这导致的内存屏障的,有时也被称为全篱笆




我不完全了解lockng一些任意对象阻止的其他的从寄存器/ CPU缓存等被缓存内存




正如您注意,在寄存器高速缓存内存或CPU缓存可能会导致奇怪的事情在多线程代码发生。 (见我的文章波动率的相关主题)的一个温柔的解释简单地说:如果一个线程,使内存页的复印件在CPU缓存的的另一个线程更改的内存中,然后第一个线程做了从缓存中读取,从而有效的第一个线程的移动在时间上向后读的。同样,写入内存可以显得的中的时间向前移动的。



一个内存屏障就像是在时间上篱笆,告诉CPU做你需要做的,以确保读取和通过时间走动写入不能搬过去是什么围栏。



这是有趣的实验是不是一个空锁,放Thread.MemoryBarrier()的调用在那里,看看会发生什么。你得到的结果相同或不同的?如果你得到同样的结果,那么它是帮助记忆障碍。如果不这样做,那么事实线程正在的几乎的同步正确地是什么是减缓下来,足以防止大多数的比赛。



我的猜测是,它是后者:空锁减缓了足够的线程下来,他们不花大部分的时间在具有竞争条件的代码。记忆障碍通常不是强大的内存模型处理器的必要。 (你是一个x86机器上,或者安腾,还是别的什么?x86上有非常强的内存模型,安腾处理器有一个需要记忆障碍弱的模式。)


First of all, I know that lock{} is synthetic sugar for Monitor class. (oh, syntactic sugar)

I was playing with simple multithreading problems and discovered that cannot totally understand how lockng some arbitrary WORD of memory secures whole other memory from being cached is registers/CPU cache etc. It's easier to use code samples to explain what I'm saying about:

for (int i = 0; i < 100 * 1000 * 1000; ++i) {
    ms_Sum += 1;
}

In the end ms_Sum will contain 100000000 which is, of course, expected.

Now we age going to execute same cycle but on 2 different threads and with upper limit halved.

for (int i = 0; i < 50 * 1000 * 1000; ++i) {
    ms_Sum += 1;
}

Because of no synchronization we get incorrect result - on my 4-core machine it is random number nearly 52 388 219 which is slightly larger than half from 100 000 000. If we enclose ms_Sum += 1; in lock {}, we, of cause, would get absolutely correct result 100 000 000. But what's interesting for me (truly saying I was expecting alike behavior) that adding lock before of after ms_Sum += 1; line makes answer almost correct:

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

    ms_Sum += 1;
}

For this case I usually get ms_Sum = 99 999 920, which is very close.

Question: why exactly lock(ms_Lock) { ms_Counter += 1; } makes program completely correct but lock(ms_Lock) {}; ms_Counter += 1; only almost correct; how locking arbitrary ms_Lock variable makes whole memory stable?

Thanks a lot!

P.S. Gone to read books about multithreading.

SIMILAR QUESTION(S)

How does the lock statement ensure intra processor synchronization?

Thread synchronization. Why exactly this lock isn't enough to synchronize threads

解决方案

why exactly does lock(ms_Lock) { ms_Counter += 1; } make the program completely correct but lock(ms_Lock) {}; ms_Counter += 1; only almost correct?

Good question! The key to understanding this is that a lock does two things:

  • It causes any thread that contests the lock to pause until the lock can be taken
  • It causes a memory barrier, also sometimes called a "full fence"

I do not totally understand how lockng some arbitrary object prevents other memory from being cached in registers/CPU cache, etc

As you note, caching memory in registers or the CPU cache can cause odd things to happen in multithreaded code. (See my article on volatility for a gentle explanation of a related topic..) Briefly: if one thread makes a copy of a page of memory in the CPU cache before another thread changes that memory, and then the first thread does a read from the cache, then effectively the first thread has moved the read backwards in time. Similarly, writes to memory can appear to be moved forwards in time.

A memory barrier is like a fence in time that tells the CPU "do what you need to do to ensure that reads and writes that are moving around through time cannot move past the fence".

An interesting experiment would be to instead of an empty lock, put a call to Thread.MemoryBarrier() in there and see what happens. Do you get the same results or different ones? If you get the same result, then it is the memory barrier that is helping. If you do not, then the fact that the threads are being almost synchronized correctly is what is slowing them down enough to prevent most races.

My guess is that it is the latter: the empty locks are slowing the threads down enough that they are not spending most of their time in the code that has a race condition. Memory barriers are not typically necessary on strong memory model processors. (Are you on an x86 machine, or an Itanium, or what? x86 machines have a very strong memory model, Itaniums have a weak model that needs memory barriers.)

这篇关于线程同步。究竟如何锁定使得对内存的访问“正确”?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆