I'd like to have a thread_local
variable to change the level of logging applied in each thread of my application. Something like so:
enum class trace_level { none, error, warning, log, debug, verbose };
static thread_local trace_level min_level = trace_level::log;
The default value should be trace_level::log
for the main thread when the application starts, but if it is changed before launching other threads, then I would like the child threads to start with the current value of the parent.
Is there any way to do this using a thread_local
variable? Since this code is buried in a library it is not an option to simply set the value manually at the start of each thread.
This already happens if the initialization is dynamic. The standard requires that variables with "thread storage duration" and dynamic initialization be initialized sometime between the start of the thread and the 'first odr-use'. However, since you generally can't control exactly when that initialization will occur (other than sometime after the thread object is created and sometime before the thread ends - assuming the thread local variable actually gets used by the thread) the problem is that the thread local variable might get initialized with a value that your main thread sets after the thread is created.
For a concrete example, consider:
With the
sleep_for()
call commented out as above, I get the following output (usually):However, with the
sleep_for()
uncommented, I get (again - usually):So as long as you're willing to live with a bit of uncertainty regarding which logging level a thread will get if the level gets changed in the main thread soon after the thread starts, you can probably just do what you're looking to do pretty naturally.
There's one remaining caveat - data races. The code above has a data race on the
log_level
variable, so it actually has undefined behavior. The fix for that is to make the variable either an atomic type or wrap it in a class that uses a mutex to protect updates and reads from data races. So change the declaration of the globallog_level
to:Standards citations:
and
You can create a global pointer to a parent thread local variable.
In global scope
Then, in each thread you can do:
(Possibly, make the
min_level_ptr
atomic for added safety and use atomic compare exchange instead of assignment).The idea goes as following: each thread's local storage occupies a different region in memory, so
min_level
variable in one thread has unique storage address different from all other.min_level_ptr
, on the other hand, has the same address, no matter which thread is accessing it. As "parent" thread starts before all other, it will claim the globally shared pointer with its ownmin_level
address. The children will then initialize their values from that location.