并非所有对象都调用析构函数thread_local

Destructor isn't called for all thread_local objects

本文关键字:析构函数 thread local 调用 对象      更新时间:2023-10-16

我在GCC 6.2.0和C++1z下使用OpenMP。我尝试在需要时使用在线程中创建thread_local对象。 thread_local对象几乎可以正常工作,但似乎只为一个线程调用析构函数。我可以用以下代码模拟问题。代码是否使用了某些不允许的功能,或者 GCC 实现可能存在一些问题?

#include <iostream>
#include <memory>
#include <mutex>
#include <thread>
#include <sstream>
std::mutex g_cerr_mutex;
struct X {
    std::string name_;
    X() {
            std::stringstream ss;
            ss << std::this_thread::get_id();
            name_ = ss.str();
    }
    ~X() noexcept {
            std::lock_guard<std::mutex> guard(g_cerr_mutex);
            std::cerr << "Destructing: " << name_ << std::endl;
    }
};
int main(void) {
    static thread_local std::unique_ptr<X> ptr;
    #pragma omp parallel for
    for (unsigned x = 0; x < 32; ++x) {
            if (!ptr) {
                    ptr.reset(new X);
            }
            std::lock_guard<std::mutex> guard(g_cerr_mutex);
            std::cerr << std::this_thread::get_id() << " : " <<  static_cast<void*>(ptr.get()) << std::endl;
    }
    return 0;
}

代码是在具有 4 核 i7 CPU 的 Linux 下编译和构建的。用于编译的命令如下所示:

$ g++ -std=gnu++1z -fopenmp -Wall -Werror -Ofast -pthread -c omp.cpp 
$ g++ -std=gnu++1z -fopenmp -Wall -Werror -Ofast -pthread omp.o -o omp

程序的输出如下所示:

139868398491392 : 0x7f35780008c0 
139868398491392 : 0x7f35780008c0 
139868398491392 : 0x7f35780008c0 
139868398491392 : 0x7f35780008c0 
139868453738496 : 0x7bc2d0 
139868453738496 : 0x7bc2d0 
139868453738496 : 0x7bc2d0 
139868453738496 : 0x7bc2d0 
139868423669504 : 0x7f35880008c0 
139868423669504 : 0x7f35880008c0 
139868423669504 : 0x7f35880008c0 
139868423669504 : 0x7f35880008c0 
139868406884096 : 0x7f35700008c0 
139868406884096 : 0x7f35700008c0 
139868406884096 : 0x7f35700008c0 
139868406884096 : 0x7f35700008c0 
139868432062208 : 0x7f35a00008c0 
139868432062208 : 0x7f35a00008c0 
139868432062208 : 0x7f35a00008c0 
139868432062208 : 0x7f35a00008c0 
139868390098688 : 0x7f35900008c0 
139868390098688 : 0x7f35900008c0 
139868390098688 : 0x7f35900008c0 
139868390098688 : 0x7f35900008c0 
139868415276800 : 0x7f35980008c0 
139868415276800 : 0x7f35980008c0 
139868415276800 : 0x7f35980008c0 
139868415276800 : 0x7f35980008c0 
139868381705984 : 0x7f35800008c0 
139868381705984 : 0x7f35800008c0 
139868381705984 : 0x7f35800008c0 
139868381705984 : 0x7f35800008c0 
Destructing: 139868453738496

显然只调用了一个析构函数。

混合C++语言线程功能和 OpenMP 没有明确定义。(请参阅相关问题)。基本上OpenMP仅指C++98,因此与OpenMP和threadlocal的交互是不安全/可移植的。通常假设它会起作用,因为实现会做正确的事情,但在这种情况下,它们显然没有。 顺便说一句:我可以在英特尔编译器/OpenMP运行时重现相同的问题。

安全和便携的方法是坚持使用纯C++17或OpenMP。使用 OpenMP,这意味着将ptr定义为私有:

static std::unique_ptr<X> ptr;
#pragma omp parallel
{
    ptr.reset();
    #pragma omp for
    for (unsigned x = 0; x < 32; ++x) {

请注意,reset是必需的,否则未定义ptr的值。您不能使用firstprivate因为std::unique_ptr没有复制 ctor。