C++ Logo

std-discussion

Advanced search

Re: Clarification needed: block scope thread_locals can be referenced for the first time without control passing through declaration

From: Brian Bi <bbi5291_at_[hidden]>
Date: Mon, 19 Aug 2019 12:46:38 -0500
On Mon, Aug 19, 2019 at 12:38 PM Princeton Ferro via Std-Discussion <
std-discussion_at_[hidden]> wrote:

> I disagree, because this is not the behaviour function-local variables
> of static-storage duration
>
> If we were to go with interpretation #2, then it would effectively mean
> that you could never use block-scope thread-local variables in a lambda,
> since (1) the standard says you can only capture variables with automatic
> storage:
>

Well, you surely can---you simply cannot execute a lambda like the one in
your example *in a new thread*. And I think that in general the compiler
cannot prove that said lambda is or is not executed in a new thread, so
this is a quality of implementation issue---if you are lucky, maybe the
compiler will catch some cases and warn you, and maybe even tell you the
safe alternative---namely, to init-capture the variable by reference.

> [expr.prim.lambda.capture - 8.1.5.2, pp 4]
> The identifier in a simple-capture is looked up using the usual rules for
> unqualified name lookup (6.4.1); each
> such lookup shall find an entity. An entity that is designated by a
> simple-capture is said to be explicitly
> captured, and shall be *this (when the simple-capture is “this” or “*
> this”) or a variable with automatic
> storage duration declared in the reaching scope of the local lambda
> expression
>
> And (2) since the use cases of lambdas almost always is to pass them to
> some non-local function.
> Would you agree at least that the standard could be revised to address
> this issue more clearly?
> ------------------------------
> *From:* Daniel Krügler <daniel.kruegler_at_[hidden]>
> *Sent:* Monday, August 19, 2019 11:43 AM
> *To:* std-discussion_at_[hidden] <std-discussion_at_[hidden]>
> *Cc:* Princeton Ferro <pferro_at_[hidden]>; pe_cce_openmp <
> pe_cce_openmp_at_[hidden]>
> *Subject:* Re: [std-discussion] Clarification needed: block scope
> thread_locals can be referenced for the first time without control passing
> through declaration
>
> 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 mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>


-- 
*Brian Bi*

Received on 2019-08-19 12:48:53