在下面的代码示例中,程序执行永远不会结束。
它创建一个线程,该线程在终止之前等待全局bool
设置为 true
。只有一个作家和一个读者。我相信允许循环继续运行的唯一情况是bool
变量是否为假。
bool
变量怎么可能最终只使用一个编写器处于不一致的状态?
#include <iostream>
#include <pthread.h>
#include <unistd.h>
bool done = false;
void * threadfunc1(void *) {
std::cout << "t1:start" << std::endl;
while(!done);
std::cout << "t1:done" << std::endl;
return NULL;
}
int main()
{
pthread_t threads;
pthread_create(&threads, NULL, threadfunc1, NULL);
sleep(1);
done = true;
std::cout << "done set to true" << std::endl;
pthread_exit(NULL);
return 0;
}
从某种意义上说,这个语句threadfunc1()
存在一个问题:
while(!done);
可以由编译器实现为:
a_register = done;
label:
if (a_register == 0) goto label;
因此,永远不会看到done
的更新。
实际上没有什么可以阻止编译器优化while循环。使用原子或互斥锁从多个线程访问布尔值。这是唯一受支持且正确的解决方案。当您使用 posix 时,在这种情况下,互斥锁将是正确的解决方案。
并且不要使用volatile
.有一个 posix 标准规定了什么必须有效,volatile
不是一个有保证工作的解决方案。
还有一个问题:在将标志设置为 false 之前,无法保证新创建的线程每次都开始运行。
对于这样简单的例子,易失性就足够了。但对于绝大多数现实世界的情况来说,事实并非如此。为此任务使用条件变量。乍一看,它们看起来很奇怪,但实际上它们非常合乎逻辑。在 x86 上,布尔是原子的读/写(对于 ARM,可能不是)。向量也存在一个障碍:它不是布尔值的向量,它是一个位域。要从多个线程写入向量,请使用 vector(或 bool arr[SIZE])。你也没有加入线程,这是错误的。
争用条件意味着:当两个线程访问同一个对象,并且其中至少有一个是写入时。
这意味着您将有两种类型的赛车,写-写冲突和写-读冲突。
回到你的代码,你基本上有两个线程,一个是主线程,另一个是你用pthread_create创建的线程。
其中一个是read: while(!done),另一个是write: done = true。
你肯定有比赛条件。
在 c++ 中只有一个线程写入布尔变量时,是否存在竞争条件?
是的。在您的情况下,主线程也是一个线程(即您有一个线程写入和一个线程读取)。
布尔变量怎么可能最终只用一个编写器处于不一致的状态?
编译器是(应该是)优化编译器。它可能会优化done变量的读取,除非您注意避免这种情况(改用std::atomic<bool> done
)。
不能保证分配给一个字节的布尔值是原子的