mar*_*ack 6 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;
Run Code Online (Sandbox Code Playgroud)
trace_level::log应用程序启动时,默认值应该是主线程,但如果在启动其他线程之前更改它,那么我希望子线程以父项的当前值开始.
有没有办法使用thread_local变量?由于此代码隐藏在库中,因此无法在每个线程的开头手动设置值.
如果初始化是动态的,这种情况就已经发生了。该标准要求在线程启动和“第一次使用 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();
}
Run Code Online (Sandbox Code Playgroud)
通过sleep_for()如上所述注释掉调用,我会得到以下输出(通常):
C:\so-test>test
thread 1, min_level == 5
thread 2, min_level == 5
Run Code Online (Sandbox Code Playgroud)
然而,由于sleep_for()未注释,我得到(再次 - 通常):
C:\so-test>test
thread 1, min_level == 3
thread 2, min_level == 5
Run Code Online (Sandbox Code Playgroud)
因此,只要您愿意忍受在线程启动后不久主线程中的日志记录级别发生更改时线程将获得哪个日志记录级别的一些不确定性,您就可以做您想做的事情很自然。
还有一个需要注意的地方——数据竞争。上面的代码在log_level变量上存在数据竞争,因此它实际上具有未定义的行为。解决这个问题的方法是将变量设置为原子类型,或者将其包装在使用互斥锁的类中,以保护数据争用的更新和读取。因此将全局声明更改 log_level为:
std::atomic<trace_level> log_level(trace_level::log);
Run Code Online (Sandbox Code Playgroud)
标准引用:
3.6.2 非局部变量的初始化[basic.start.init]
...具有线程存储持续时间的非局部变量在线程执行时被初始化。...
和
3.7.2/2 线程存储持续时间[basic.stc.thread]
具有线程存储持续时间的变量应在其第一次 odr 使用(3.2)之前初始化,并且如果构造,应在线程退出时销毁。