线程同步——微妙的问题
让我有这个循环:
static a;
for (static int i=0; i<10; i++)
{
a++;
///// point A
}
到这个循环2个线程进入...
我不确定某事...如果thread1进入POINT A,留在那里,而THREAD2进入循环10次,会发生什么,但是在将 i 的值增加到 10 后的第 10 个循环之后,在检查 i 的值是否小于 10 之前, Thread1 正在退出循环,并假设增加 i 并再次进入循环。 Thread1 将增加的值是多少(我将看到)?是 10 还是 0 ?
线程 1 是否有可能将 i 增加到 1,然后线程 2 再次进入循环 9 次(可能是 8 ,7 等...)
谢谢
let's i have this loop :
static a;
for (static int i=0; i<10; i++)
{
a++;
///// point A
}
to this loop 2 threads enters...
i'm not sure about something.... what will happen in case thread1 gets into POINT A , stay there, while THREAD2 gets into the loop 10 times, but after the 10'th loop after incrementing i's value to 10, before checking i's value if it's less then 10,
Thread1 is getting out of the loop and suppose to increment i and get into the loop again.
what's the value that Thread1 will increment (which i will he see) ? will it be 10 or 0 ?
is it posibble that Thread1 will increment i to 1, and then thread 2 will get to the loop again for 9 times (and them maybe 8 ,7 , etc...)
thanks
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
您必须认识到增量操作实际上是有效的:
您必须问自己,如果其中两个同时发生在两个独立线程中,会发生什么:
对于两个同时增量,您可以看到其中一个可能是因为两个线程都读取了预递增的值,所以会丢失。
您给出的示例因静态循环索引而变得复杂,我一开始没有注意到。
由于这是 C++ 代码,标准实现是静态变量对所有线程都可见,因此所有线程只有一个循环计数变量。明智的做法是使用普通的自动变量,因为每个线程都有自己的变量,不需要锁定。
这意味着虽然有时会丢失增量,但也可能会获得增量,因为循环本身可能会丢失计数并迭代额外的次数。总而言之,这是一个关于不该做什么的很好的例子。
You have to realize that an increment operation is effectively really:
You have to ask yourself, what happens if two of these happen in two independent threads at the same time:
For two simultaneous increments, you can see that it is possible that one of them gets lost because both threads read the pre-incremented value.
The example you gave is complicated by the static loop index, which I didn't notice at first.
Since this is c++ code, standard implementation is that the static variables are visible to all threads, thus there is only one loop counting variable for all threads. The sane thing to do would be to use a normal auto variable, because each thread would have its own, no locking required.
That means that while you will lose increments sometimes, you also may gain them because the loop itself may lose count and iterate extra times. All in all, a great example of what not to do.
如果
i
在多个线程之间共享,那么一切都将失败。任何线程都可以在另一个线程执行过程中的任何时刻(包括该线程增量操作的中途)增加i
。没有任何有意义的方法来推理上面代码中的i
的内容。不要那样做。要么为每个线程提供自己的i
副本,要么将增量和与 10 的比较作为单个原子操作。If
i
is shared between multiple threads, all bets are off. It's possible for any thread to incrementi
at essentially any point during another thread's execution (including halfway through that thread's increment operation). There is no meaningful way to reason about the contents ofi
in the above code. Don't do that. Either give each thread its own copy ofi
, or make the increment and comparison with 10 a single atomic operation.这实际上并不是一个微妙的问题,因为如果同步将成为一个问题,您将永远不会在实际代码中允许这样做。
It's not really a delicate issue because you would never allow this in real code if the synchronization was going to be an issue.
我将在循环中使用
i++
:因为它模仿
a
。 (注意,这里的static
很奇怪)考虑线程A是否在到达
i++
时被挂起。线程 B 使i
一直到 9,进入i++
并使其变为 10。如果它继续前进,则循环将存在。啊,但是现在线程 A 恢复了!所以它从中断的地方继续:增加i
!所以i
变成了11,你的循环就中断了。每当线程共享数据时,都需要对其进行保护。您还可以使
i++
和i
10
如果您的平台支持,则以原子方式发生(永远不会被中断)。I'm just going to use
i++
in your loop:Because it mimics
a
. (Note,static
here is very strange)Consider if Thread A is suspended just as it reaches
i++
. Thread B getsi
all the way to 9, goes intoi++
and makes it 10. If it got to move on, the loop would exist. Ah, but now Thread A is resumed! So it continues where it left off: incrementi
! Soi
becomes 11, and your loop is borked.Any time threads share data, it needs to be protected. You could also make
i++
andi < 10
happen atomically (never be interrupted), if your platform supports it.您应该使用互斥来解决这个问题。
You should use mutual exclusion to solve this problem.
这就是为什么在多线程环境中,我们应该使用锁。
在您的情况下,您应该这样写:
现在问题消失了.. 请注意,
lock()
和unlock()
应该锁定和解锁所有尝试的线程所共有的互斥体访问我!And that is why, on multi-threaded environment, we are suppose to use locks.
In your case, you should write:
Now the problem disappears .. Note that
lock()
andunlock()
are supposed to lock and unlock a mutex common to all threads trying to access i!是的,任何一个线程都可以完成该循环中的大部分工作。但正如 Dynite 所解释的那样,这永远不会(而且应该)出现在实际代码中。如果同步是一个问题,您应该提供互斥(Boost、pthread 或 Windows 线程)互斥体以防止此类竞争条件。
Yes, it's possible that either thread can do the majority of the work in that loop. But as Dynite explained, this would (and should) never show up in real code. If synchronization is an issue, you should provide mutual exclusion (a Boost, pthread, or Windows Thread) mutex to prevent race conditions such as this.
为什么要使用静态循环计数器?
这闻起来像家庭作业,而且是一份糟糕的作业。
Why would you use a static loop counter?
This smells like homework, and a bad one at that.
两个线程都有自己的 i 副本,因此行为可以是任何东西。这就是为什么它是一个这样的问题的部分原因。
当您使用互斥体或临界区时,线程通常会同步,但如果变量不是易失性的,则即使如此也不能绝对保证。
毫无疑问,有人会指出“易失性在多线程中没有用!”但人们说了很多愚蠢的话。你不必拥有 volatile,但它对某些事情很有帮助。
Both the threads have their own copy of i, so the behavior can be anything at all. That's part of why it's such a problem.
When you use a mutex or critical section the threads will generally sync up, but even that is not absolutely guaranteed if the variable is not volatile.
And someone will no doubt point out "volatile has no use in multithreading!" but people say lots of stupid things. You don't have to have volatile but it is helpful for some things.
如果你的“int”不是原子机器字大小(想想64位地址+模拟32位VM的数据),你将
“word-tear”
。在这种情况下,你的“int”是 32 位,但机器自动寻址 64 位。现在您必须读取全部 64 个,增加一半,然后将它们全部写回。这是一个更大的问题;如果你真的想要详细的信息,请深入了解处理器指令集,并了解 grep gcc 是如何在任何地方实现“易失性”的。
添加“易失性”并查看机器代码如何变化。如果您不看芯片寄存器,请只使用 boost 库并完成它。
If your "int" is not the atomic machine word size (think 64 bit address + data emulating a 32-bit VM) you will
"word-tear"
. In that case your "int" is 32 bits, but the machine addresses 64 atomically. Now you have to read all 64, increment half, and write them all back.This is a much larger issue; bone up on processor instruction sets, and grep gcc for how it implements "volatile" everywhere if you really want the gory details.
Add "volatile" and see how the machine code changes. If you aren't looking down at the chip registers, please just use boost libraries and be done with it.
如果您需要同时使用多个线程递增一个值,请查找“原子操作”。对于 Linux,查找“gcc 原子操作”。大多数平台都支持原子递增、添加、比较和交换等操作的硬件。对于这个来说,锁定会太过分......atomic inc 比 lock inc 解锁快很多。如果您必须同时更改许多字段,则可能需要锁,尽管您可以使用大多数原子操作一次更改 128 位的字段。
易失性与原子操作不同。 Volatile 帮助编译器知道什么时候使用变量的副本是一个坏主意。在其用途中,当您有多个线程更改您希望在不锁定的情况下读取“最新版本”的数据时,易失性非常重要。 Volatile 仍然无法解决你的 a++ 问题,因为两个线程可以同时读取“a”的值,然后都增加相同的“a”,然后最后一个写入“a”的线程获胜,你就失去了一个 inc。易失性会通过不允许编译器将值保存在寄存器等中来减慢优化代码的速度。
If you need to increment a value with multiple threads at the same time, then Look up "atomic operations". For linux, look up "gcc atomic operations". There is hardware support on most platforms to atomicly increment, add, compare and swap, and more. LOCKING WOULD BE OVERKILL for this....atomic inc is magnitudes faster than lock inc unlock. If you have to change a lot of fields at the same time you may need a lock, although you can change 128 bits worth of fields at a time with most atomic ops.
volatile is not the same as an atomic operation. Volatile helps the compiler know when its a bad idea to use a copy of a variable. Among its uses, volatile is important when you have multiple threads changing data that you would like to read the "most up to date version of" of without locking. Volatile will still not fix your a++ problem as two threads can read the value of "a" at the same time and then both increment the same "a" and then the last one to write "a" wins and you lost an inc. Volatile will slow down optimized code by not letting the compiler hold values in registers and what not.