Search code examples
c++c++11language-lawyerstdthreadthread-local-storage

Capturing a `thread_local` in a lambda


Capturing a thread_local in a lambda:

#include <iostream>
#include <thread>
#include <string>

struct Person
{
    std::string name;
};

int main()
{
    thread_local Person user{"mike"};
    Person& referenceToUser = user;

    // Works fine - Prints "Hello mike"
    std::thread([&]() {std::cout << "Hello " << referenceToUser.name << std::endl;}).join();

    // Doesn't work - Prints "Hello"
    std::thread([&]() {std::cout << "Hello " << user.name << std::endl;}).join();

    // Works fine - Prints "Hello mike"
    std::thread([&user=user]() {std::cout << "Hello " << user.name << std::endl;}).join();
}

https://godbolt.org/z/zeocG5ohb

It seems like if I use the original name of a thread_local then its value on the thread which executes the lambda is the thread_local version of the thread which is running the lambda. But as soon as I take a reference or pointer to the thread local it turns into (a pointer to) the originating threads instance.

What are the rules here. Can I rely on this analysis?


Solution

  • Similar to local static objects, local thread_local (implicitly static thread_local) objects are initialized when control passes through their declaration for the first time.

    The thread you are creating never executes main, only the main thread does, so you're accessing user before its lifetime has begun on the extra thread.

    Your three cases explained

    std::thread([&]() {std::cout << "Hello " << referenceToUser.name << std::endl;}).join();
    

    We are capturing referenceToUser which refers to the user on the main thread. This is okay.

    std::thread([&]() {std::cout << "Hello " << user.name << std::endl;}).join();
    

    We are accessing user on the extra thread before its lifetime has begun. This is undefined behavior.

    std::thread([&user=user]() {std::cout << "Hello " << user.name << std::endl;}).join();
    

    Once again, we are referencing the user from the main thread here, which is same as the first case.

    Possible fix

    If you declare user outside of main, then user will be initialized when your thread starts, not when main runs:

    thread_local Person user{"mike"};
    
    int main() {
        // ...
    

    Alternatively, declare user inside of your lambda expression.


    Note: It's not necessary to capture thread_local objects. The second example could be [] { ... }.