[英]Why it doesn't work? Simple multithreading example
你能帮我理解为什么这段代码会冻结程序吗?
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int i = 0;
mutex mx;
void foo() {
while(1) {
lock_guard<mutex> locker(mx);
i++;
if(i == 5000) {
break;
}
}
}
void boo() {
while(1) {
if(i == 100) {
lock_guard<mutex> locker(mx);
i = 5000;
break;
}
}
}
int main(int argc, char *argv[])
{
thread th1(foo);
thread th2(boo);
th1.join();
th2.join();
return 0;
}
为什么我会有这样的结果? 如何更改代码以使其正确? 你能告诉我你的想法吗? 谢谢。
即使boo
先开始运行,它也可能永远不会看到i==100
。
如果您只有一个 CPU,那么当i==100
时,CPU 不太可能从foo
切换到boo
。
如果你有多个 CPU,那么i==100
可能永远不会进入foo's
缓存,因为i
不是易失性的,并且互斥锁在读取之间没有锁定。
实际上,编译器在第一次之后甚至不必读取i
,因为没有 memory 障碍。 它可以假设值没有改变。
即使你要解决这个问题,在boo
注意到之前i
可能会增加超过 100 的明显可能性仍然存在。 看起来您希望这两个线程“轮流”,但事实并非如此。
您的解决方案存在一些并发问题:
您必须始终如一地锁定互斥体。 所有对i
的访问都必须受到互斥量的保护,因此在if (i == 100) {
行也是如此。 在没有同步的情况下,编译器可以自由地优化线程,就好像它在孤立地运行一样,并假设i
永远不会改变。
不能保证boo
会在foo
之前开始。 如果它在之后开始, i
将已经增加到远高于100
。
不能保证互斥锁定是公平的。 竞争同一个互斥量的两个线程不会以交错方式运行。 这意味着foo
可能会在boo
有机会运行之前多次增加i
,因此boo
看到的i
的值可能很容易从0
跳到1000
,跳过所需的100
。
孤立地, foo
将“逃跑”,将i
增加到远远超过5000
。 应该有一些退出或重新启动条件。
如何更改代码以使其正确?
添加一些同步以强制交错处理。 例如,使用condition_variable
s 在线程之间发出信号:
int i = 0;
mutex mx;
condition_variable updated_cond;
bool updated = false;
condition_variable consumed_cond;
bool consumed = true;
void foo() {
while (1) {
unique_lock<mutex> locker(mx);
consumed_cond.wait(locker, [] { return consumed; });
consumed = false;
if (i == 5000) {
break;
}
std::cout << "foo: i = " << i << "+1\n";
i++;
updated = true;
updated_cond.notify_one();
}
std::cout << "foo exiting\n";
}
void boo() {
for (bool exit = false; !exit; ) {
unique_lock<mutex> locker(mx);
updated_cond.wait(locker, [] { return updated; });
updated = false;
std::cout << "boo: i = " << i << "\n";
if (i == 100) {
i = 5000;
exit = true;
}
consumed = true;
consumed_cond.notify_one();
}
std::cout << "boo exiting\n";
}
该程序的行为是未定义的,因此对其所做的推理是徒劳的。 问题是boo
读取i
的值, foo
都读取和写入i
的值,但是boo
中if (i == 100)
中i
的读取相对于发生在foo
中的写入是无序的。 那是一场数据竞赛,程序的行为是未定义的。 当然,您可以猜测可能会发生什么,但如果您希望代码正确运行,则必须确保不存在数据竞争。 这意味着使用某种形式的同步:要么将boo
中的锁移到if
之前,要么摆脱互斥并将i
的类型更改为std::atomic<int>
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.