Search code examples
c++multithreadingc++11thread-local

Thread local variable accessed through non-thread local object


Let's start with a piece of code (Coliru):

#include <iostream>
#include <thread>

using namespace std;

struct A
{
    thread_local static A* p_a;
    thread_local static int i;
};

thread_local int A::i;
thread_local A* A::p_a;

int main( )
{
    A::p_a = new A;

    auto lambda = [](A* a)
        {
          a->i = 1; // Prints 1 (below, of course)
          std::cout << a->i << std::endl;
        };

    std::thread t(std::bind(lambda, A::p_a));
    t.join();

    // Prints 0 (it hasn't been modified)
    std::cout << A::p_a->i << std::endl;

    return 0;
}

As all of you can see, the second thread modifies its thread local copy of A::i, even though I accessed it from another thread local object of another thread. Is it the expected behaviour? Because it makes impossible to get info from another thread using a "referer" unless I pass a pointer or reference to the foreign's thread_local object which I want to read.

With "referer" I refer something which manages or can give you access to its own thread_local variable from its thread. But that's impossible!! Any expression yielding a thread_local variable, no matter from whom (I have done different test, even with accessor functions), ends by using the thread_local instance of the reading thread.


Solution

  • This is a syntax issue: In this case a->i; is identical to A::i; because A::i is a static member and its address is not dependant on any one instance of A.

    So just because you are using syntax that looks like you are dereferencing an A pointer, you're not. The compiler sees it is a static member and ignores the pointer and goes straight for the single (per thread in this case) static instance. The whole thread_local business is actually irrelevant to that.

    So when you are accessing a static member of A through A* a in your lambda, the compiler is ignoring the address you gave it and doing A::i regardless (getting its own thread_local version).

    struct A
    {
        static int i;
    };
    
    A* a = new A;
    a->i; // identical to A::i (because i is static)
    

    This is standard syntax as mentioned here in the C++14 Standard:

    5.2.5 Class member access [ expr.ref ]

    1. A postfix expression followed by a dot . or an arrow ->, optionally followed by the keyword template (14.2), and then followed by an id-expression, is a postfix expression. The postfix expression before the dot or arrow is evaluated; 65 the result of that evaluation, together with the id-expression, determines the result of the entire postfix expression.

    ...

    65) If the class member access expression is evaluated, the subexpression evaluation happens even if the result is unnecessary to determine the value of the entire postfix expression, for example if the id-expression denotes a static member.

    (emphasis mine)