C ++ 11 thread_local变量可以从父线程继承其初始值吗?

时间:2014-05-09 02:15:16

标签: c++ multithreading c++11

我希望有一个thread_local变量来更改应用程序的每个线程中应用的日志记录级别。像这样:

enum class trace_level { none, error, warning, log, debug, verbose };
static thread_local trace_level min_level = trace_level::log;

应用程序启动时主线程的默认值应为trace_level::log,但如果在启动其他线程之前更改它,那么我希望子线程以父级的当前值开始。 / p>

有没有办法使用thread_local变量?由于此代码隐藏在库中,因此无法在每个线程的开头手动设置值。

2 个答案:

答案 0 :(得分:1)

您可以创建指向父线程局部变量的全局指针。

在全球范围内

thread_local trace_level min_level = trace_level::log;
trace_level *min_level_ptr = nullptr;

然后,在每个线程中你可以做到:

if (!min_level_ptr)
    min_level_ptr = &min_level;
else
    min_level = *min_level_ptr;

(可能使min_level_ptr原子为了增加安全性并使用原子比较交换而不是赋值。

这个想法如下:每个线程的本地存储占用内存中的不同区域,因此一个线程中的min_level变量具有与其他线程不同的唯一存储地址。另一方面,min_level_ptr具有相同的地址,无论哪个线程正在访问它。当“父”线程在所有其他线程之前启动时,它将声明具有其自己的min_level地址的全局共享指针。然后,孩子们将从该位置初始化他们的值。

答案 1 :(得分:1)

如果初始化是动态的,则会发生这种情况。该标准要求变量具有"线程存储持续时间"并且在线程开始和第一次使用odr之间的某个时间初始化动态初始化。但是,由于您通常无法准确控制何时进行初始化(除了在线程对象创建之后的某个时间以及线程结束之前的某个时间 - 假设线程本地变量实际被线程使用)问题是线程局部变量可能会使用一个值进行初始化,主线程在创建线程后设置

有关具体示例,请考虑:

#include <stdio.h>

#include <chrono>
#include <functional>
#include <thread>
#include <string>

using std::string;

enum class trace_level { none, error, warning, log, debug, verbose };

trace_level log_level = trace_level::log;


static thread_local trace_level min_level = log_level;
void f(string const& s)
{

    printf("%s, min_level == %d\n", s.c_str(), (int) min_level);
}



int main()
{
    std::thread t1{std::bind(f,"thread 1")};

    //TODO: std::this_thread::sleep_for(std::chrono::milliseconds(50));

    log_level = trace_level::verbose;
    std::thread t2{std::bind(f,"thread 2")};

    t1.join();
    t2.join();
}

如上所述注释sleep_for(),我得到以下输出(通常):

C:\so-test>test
thread 1, min_level  == 5
thread 2, min_level  == 5

但是,如果sleep_for()取消注释,我会(再次 - 通常):

C:\so-test>test
thread 1, min_level  == 3
thread 2, min_level  == 5

因此,只要您在线程启动后不久主线程中的级别发生变化,您是否愿意接受线程将获得哪些日志记录级别的不确定性,您可能只需要执行以下操作即可#39;希望自然而然地做。

还有一个警告 - 数据竞赛。上面的代码在log_level变量上有一个数据竞争,所以它实际上有未定义的行为。解决方法是将变量设置为原子类型,或将其包装在使用互斥锁来保护数据争用更新和读取的类中。因此,将全局log_level的声明更改为:

std::atomic<trace_level> log_level(trace_level::log);

标准引用:

  

3.6.2非局部变量的初始化[basic.start.init]

     

...初始化具有线程存储持续时间的非局部变量   作为线程执行的结果。 ...

  

3.7.2 / 2线程存储持续时间[basic.stc.thread]

     

具有线程存储持续时间的变量应在之前初始化   它的第一次使用(3.2),如果构造,将被销毁   线程退出。