如何在使用全局变量作为参数时禁止c++编译器优化

How to forbid C++ compiler optimizing when using global variable as parameters

本文关键字:参数 禁止 c++ 优化 编译器 全局变量      更新时间:2023-10-16

我发现在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,它确实有效。我不想使用互斥锁,因为在每个循环中使用互斥锁会影响性能。

事实上,我想这样做:

  1. 一个工作线程循环通过一个全局列表,并在每个while循环中执行一些操作。(我不想在这里使用互斥锁,因为即使错误发生在一个循环中也没关系)

  2. 一些其他线程可能会添加项目到此列表。这里使用互斥锁是可以的。因为每个线程只添加一次时间)

我应该怎么做?

当前代码允许编译器进行优化,就好像没有线程一样。所以当编译器看到一个条件不变的循环时,它就可以把它优化掉。或者,正如您所观察到的行为一样,将条件中预期的内存取出替换为寄存器中的值。

一种方法是使用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(); }
}

因为编译器对线程一无所知。它所做的就是编译命令它编译的代码。

允许编译器优化掉冗余代码。在这里,由于编译器知道它检索了指针指向的值,所以它不需要再做一次。

有几种方法可以正确地做到这一点。

  1. 使用volatile,一个volatile限定符显式地告诉编译器不要优化掉对volatile对象的任何访问。

  2. volatile关键字本身并不总是足以正确实现多线程执行。虽然您可以在这里使用它,但是正确排序多线程执行的方法是使用互斥锁和条件变量。你可以在你喜欢的c++书中找到完整的描述