首页 > 解决方案 > thread_local in C++ 11 isn't consistent

问题描述

Why the output isn't consistently 1, when I declared Counter object as 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;
}

Output :

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

标签: c++multithreadingc++11thread-local

解决方案


我从探索中得到的唯一有趣的事情是 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”是一个全局计数器对象——它应该在所有线程中创建,包括主线程。

但是,当我使用 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”Counter 对象从未在 gcc 和 clang 下的主线程中创建和销毁,但它在 MSVC 下的主线程中创建和销毁。

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

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

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

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

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

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

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

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


推荐阅读