我发现在c++中使用全局变量作为参数时有一个奇怪的现象。下面是代码。如果使用-O2,程序将永远不会结束。
#include <thread>
#include <iostream>
using namespace std;
#define nthreads 2
struct Global{
int a[nthreads];
};
void func(int* a){
while(*a == 0){
}
cout << "done" << endl;
}
struct Global global;
int main(){
thread pid[nthreads];
memset(global.a, 0, nthreads*sizeof(int));
for(int i=0; i<nthreads; i++){
pid[i] = std::thread(func, &global.a[i]);
}
sleep(2);
cout << "finished" << endl;
memset(global.a, 1, nthreads*sizeof(int));
for(int i=0; i<nthreads; i++){
pid[i].join();
}
return 0;
}
如果使用- 0,一切似乎都没问题。在while循环中打印变量*a,仍然可以。
所以我猜一定是c++优化的问题。但是,编译器如何对全局变量和多线程进行如此激进的优化呢?
感谢所有的答案和评论,我试着使用volatile,它确实有效。我不想使用互斥锁,因为在每个循环中使用互斥锁会影响性能。
事实上,我想这样做:
一个工作线程循环通过一个全局列表,并在每个while循环中执行一些操作。(我不想在这里使用互斥锁,因为即使错误发生在一个循环中也没关系)
一些其他线程可能会添加项目到此列表。这里使用互斥锁是可以的。因为每个线程只添加一次时间)
我应该怎么做?
当前代码允许编译器进行优化,就好像没有线程一样。所以当编译器看到一个条件不变的循环时,它就可以把它优化掉。或者,正如您所观察到的行为一样,将条件中预期的内存取出替换为寄存器中的值。
一种方法是使用std::atomic
。
出于学习和探索的目的,我只在现代c++中涉足多线程,但这段代码可以工作:
#include <atomic>
#include <array>
#include <thread>
#include <iostream>
using namespace std;
int const nthreads = 2;
void func( atomic<int>* a )
{
while( a->load() == 0 )
{}
cout << "done" << endl;
}
namespace global {
array<atomic<int>, nthreads> a; // Zero-initialized automatically.
} // namespace global
auto main()
-> int
{
using namespace std::chrono_literals;
thread pid[nthreads];
for( int i = 0; i < nthreads; ++i )
{
pid[i] = thread( func, &global::a[i] );
}
this_thread::sleep_for( 2ms );
cout << "finished" << endl;
for( auto& item : global::a )
{
item = ( int( unsigned( -1 ) & 0x0101010101010101 ) );
}
for( int i = 0; i < nthreads; ++i) { pid[i].join(); }
}
因为编译器对线程一无所知。它所做的就是编译命令它编译的代码。
允许编译器优化掉冗余代码。在这里,由于编译器知道它检索了指针指向的值,所以它不需要再做一次。
有几种方法可以正确地做到这一点。
-
使用
volatile
,一个volatile限定符显式地告诉编译器不要优化掉对volatile对象的任何访问。 -
volatile
关键字本身并不总是足以正确实现多线程执行。虽然您可以在这里使用它,但是正确排序多线程执行的方法是使用互斥锁和条件变量。你可以在你喜欢的c++书中找到完整的描述