C++ 11 中的 thread_local 不一致

问题描述

当我将 1 对象声明Counter 时,为什么输出不一致 thread_local

#include <iostream>
#include <thread>
#define MAX 10
    
class Counter {
    int c;
public:
    Counter() : c(0) {}

    void increment() {
        ++c;
    }

    ~Counter() {   
        std::cout << "Thread " << std::this_thread::get_id() << " having counter value " << c << "\n";  
    }
};
    
thread_local Counter c;
    
void doWork() {
    c.increment();
}
    
int main() {
    std::thread t[MAX];
    for ( int i = 0; i < MAX; i++ )
        t[i] = std::thread(doWork);
    for ( int i = 0; i < MAX; i++ )
        t[i].join();    
    return 0;
}

输出

Thread 2 having counter value 19469616
Thread 3 having counter value 1
Thread 4 having counter value 19464528
Thread 5 having counter value 19464528
Thread 7 having counter value 1
Thread 6 having counter value 1
Thread 8 having counter value 1
Thread 9 having counter value 1
Thread 10 having counter value 1
Thread 11 having counter value 1

解决方法

我从探索中得到的唯一有趣的事情是 MSVC 的运行方式与 clang/gcc 不同。

这是 MSVC 在最新编译器上的输出:

Thread 34316 having counter value 1
Thread 34312 having counter value 1
Thread 34320 having counter value 1
Thread 34328 having counter value 1
Thread 34324 having counter value 1
Thread 34332 having counter value 1
Thread 34336 having counter value 1
Thread 34340 having counter value 1
Thread 34344 having counter value 1
Thread 34348 having counter value 1
Thread 29300 having counter value 0

c:\dv\TestCpp\out\build\x64-Debug (default)\TestCpp.exe (process 27748) exited with code 0.

注意:“线程 29300 的计数器值为 0”。这是主线程。由于 doWork() 从未在主线程中被调用,因此计数器值为 0。这正如我所期望的那样工作。 "c" 是一个全局 Counter 对象 - 它应该在所有线程中创建,包括主线程。

但是,当我使用 rextester.com 在 clang 和 gcc 下运行(并且 Godbolt 似乎不喜欢运行多线程程序,AFAICT)时,我得到:

Thread 140199006193408 having counter value 1
Thread 140198906181376 having counter value 1
Thread 140198806169344 having counter value 1
Thread 140198706157312 having counter value 1
Thread 140199106205440 having counter value 1
Thread 140199206217472 having counter value 1
Thread 140199306229504 having counter value 1
Thread 140199406241536 having counter value 1
Thread 140199506253568 having counter value 1
Thread 140199606265600 having counter value 1

注意:没有计数器值为 0 的主线程。“c”计数器对象从未在 gcc 和 clang 下的主线程中创建和销毁,但它是在 MSVC 下的主线程中创建和销毁的。

我相信您的个人错误是您使用的旧版本编译器存在错误。无论我使用哪种最新版本的各种类型的编译器,我都无法重现您的错误,但我将向 MSVC 团队提供一个错误,因为这是标准 C++,并且有人认为无论如何它都应该产生相同的结果编译器,它没有。

似乎(检查 https://en.cppreference.com/w/cpp/language/storage_duration)MSVC 可能做错了,因为从未在主线程中显式访问 thread_local“Counter c”对象,但该对象已被创建和销毁。

我们会看看他们怎么说 - 我还有一些其他的错误等待他们......

进一步研究:cpp 参考文章似乎表明,由于这是一个“纯全局”——即它不是本地静态线程本地对象,例如:

Foo &
GetFooThreadSingleton()
{
  static thread_local Foo foo;
  return foo;
}

然后我阅读 cppreference 文章的方式是:它是一个全局变量,就像任何其他全局变量一样,在这种情况下,MSVC 是正确的。 如果我错了,请有人纠正我。谢谢。

也没有在 gdb 内部的 Ubuntu 中本地运行它并检查我不能确定全局没有在 gcc/clang 下的主线程中实际创建和销毁,因为它可能是我们由于某种原因错过了该输出.我承认这似乎不太可能。我稍后会尝试这个,因为这个问题让我有点兴趣。

我在 Ubuntu 中本地运行它并得到了我的预期:只有在主线程中访问全局“Counter c”时才会创建和销毁它。所以我的想法是,此时这是 gcc/clang 中的一个错误。