C++ Logo

STD-DISCUSSION

Advanced search

Subject: Re: Clarification needed: block scope thread_locals can be referenced for the first time without control passing through declaration
From: Daniel Krügler (daniel.kruegler_at_[hidden])
Date: 2019-08-19 11:43:46


Am Mo., 19. Aug. 2019 um 18:22 Uhr schrieb Princeton Ferro via
Std-Discussion <std-discussion_at_[hidden]>:
>
> The standard (C++17) says that variables with thread-storage duration must be initialized before their first ODR use:
>
> [basic.std.thread - 6.7.2, pp 2]
> A variable with thread storage duration shall be initialized before its first odr-use (6.2) and, if constructed,
> shall be destroyed on thread exit.
>
>
> Later, the standard says that for thread-local variables with block scope, they must be initialized when control first passes through their declaration:
>
> [stmt.dcl - 9.7, pp 4]
> Dynamic initialization of a block-scope variable with static storage duration (6.7.1) or thread storage
> duration (6.7.2) is performed the first time control passes through its declaration
>
>
> Now consider the following example:
>
> #include <thread>
> #include <iostream>
>
> struct Object {
> int i;
> Object() : i(3) {}
> };
>
> int main(void) {
> static thread_local Object o;
>
> std::cout << "[main] o.i = " << o.i << std::endl;
> std::thread t([] { std::cout << "[new thread] o.i = " << o.i << std::endl; });
> t.join();
> }
>
>
> o is a block-scope variable with thread storage and a dynamic initializer. The lambda passed into std::thread's constructor refers to o but does not capture it, which should be fine since o is not a variable with automatic storage. However, when control passes through the lambda, it will do so on a new thread. When that happens, it will refer to o for the first time. Because o is a thread-local variable, (6.7.2) implies it should be initialized, but because it is declared in block scope, (9.7) implies that it can only be initialized when control passes through its declaration, which will never happen on the new thread.
>
> What should be done? A few options I can think of:
>
> Because it has thread storage, the variable must be initialized by the time it is first used on any thread. Essentially, the treatment should be the same as if o was declared outside the body of main().

I disagree, because this is not the behaviour function-local variables
of static-storage duration

> It is an error to refer to a block-scope thread-local variable within a lambda that escapes the block it was declared in.

Exactly that. To reduce the risk that this happens, a popular
technique is to use Meyer's singleton approach for variables of thread
local storage as well such as in the following example

[..]

Object& get_object()
{
  thread_local Object o;
  return o;
}

int main(void) {
    std::cout << "[main] o.i = " << get_object().i << std::endl;
    std::thread t([] { std::cout << "[new thread] o.i = " <<
get_object().i << std::endl; });
    t.join();
}

- Daniel


STD-DISCUSSION list run by herb.sutter at gmail.com

Older Archives on Google Groups