我希望有一个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
变量?由于此代码隐藏在库中,因此无法在每个线程的开头手动设置值。
答案 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),如果构造,将被销毁 线程退出。