4

I have a variable in file tracker.hpp:

namespace TRIALS
{
    static thread_local int a = -1;
}

I have another class in file called EMP in ema.hpp/ema.cpp

namespace Algo
{
    class EMP
    {
        public:
            void Sample();
    };
}
namespace Algo
{
    void EMP::Sample()
    {
        std::cout << "model " << std::this_thread::get_id() << " " << &TRIALS::a << " " << TRIALS::a << std::endl;
    }
}

Then my main file I have


auto model = Algo::EMP();

void Simulate(const int a)
{
    TRIALS::a = a;
    model.Sample()
    std::cout << "worker " << std::this_thread::get_id() << " " << &TRIALS::a << " " << TRIALS::a << std::endl;
}

int main()
{
    std::cout << &TRIALS::a << std::endl;
    const int nthreads = 1;

    std::cout << "main " << std::this_thread::get_id() << " " << &TRIALS::a << " " << TRIALS::a << std::endl;

    std::vector<std::thread> threads;
    for(int i=0; i<nthreads; ++i)
    {
        threads.emplace_back(&Simulate, i);
    }

    for(auto &thread : threads)
    {
        thread.join();
    }

    std::cout << "main " << std::this_thread::get_id() << " " << &TRIALS::a << " " << TRIALS::a << std::endl;

    return 0;
}

I am just running one thread for debugging but this is the output:

0x7f9540b621d8

main 140279012532800 0x7f9540b621d8 -1 (As expected)

model 140278985606912 0x7f953f1b469c -1 (Shouldn't this be 0??)

worker 140278985606912 0x7f953f1b4698 0 (As expected)

main 140279012532800 0x7f9540b621d8 -1 (As expected)

I was under the impression that each thread has it's own local copy of TRIALS::a. The a in model correctly gets incremented but when it returns from the function in the same thread, the value is still 0. I am printing out the thread ids and the address of a for good measure and I am seeing that there are actually 3 different versions of TRIALS::a despite only two total threads.

As a bonus question, what is the difference between static thread_local int a and thread_local int a ?

john
  • 169
  • 12
  • 1
    Do you know what `static` variable is? – Algirdas Preidžius Oct 07 '19 at 13:05
  • What kind of compiler/standard library/target operating system do you use. Thread local is really run-time specific thing. – Victor Gubin Oct 07 '19 at 13:06
  • i'm using gcc 7.4, with ubuntu 18.04 – john Oct 07 '19 at 13:07
  • @AlgirdasPreidžius I know in the context of normal static, I would think that thread_local static would have just one copy of the variable in question for each thread – john Oct 07 '19 at 13:08
  • @john So, what does the "normal static" mean? – Algirdas Preidžius Oct 07 '19 at 13:10
  • 3
    Each `.cpp` file gets its own copy. You need `extern` instead of `static` to share a global between files. – freakish Oct 07 '19 at 13:10
  • @freakish so in ema.hpp and main.cpp I need extern thread_local int TRIALS::a; ? – john Oct 07 '19 at 13:17
  • @john You need `extern` in `.hpp` file and then you need to define (and possibly initialize) the variable in one of the `.cpp` files. – freakish Oct 07 '19 at 13:18
  • @AlgirdasPreidžius I was under the impression that static indicates that the variable is only init once. So every time I reference TRIALS::a it is the same memory location – john Oct 07 '19 at 13:19
  • 2
    @john on the contrary. `static` here means "not global". See this: https://stackoverflow.com/questions/14349877/static-global-variables-in-c/14349916 `static` is weird and context dependent. One of the worst C++ constructs. – freakish Oct 07 '19 at 13:21
  • @john "_I was under the impression that static indicates that the variable is only init once._" I am wondering now.. What gave you such impression? What gave you an impression, that non-static variables might be initialized twice? – Algirdas Preidžius Oct 07 '19 at 13:29

1 Answers1

7

In your example static makes that thread_local object use internal linkage, so that each translation unit (.cpp file) has its own copy of the variable.

See storage class specifiers for details:

The thread_local keyword is only allowed for objects declared at namespace scope, objects declared at block scope, and static data members. It indicates that the object has thread storage duration. It can be combined with static or extern to specify internal or external linkage (except for static data members which always have external linkage), respectively, but that additional static doesn't affect the storage duration.

I.e. you may like to drop that static keyword, so that you only have one copy of the object in the entire application. In the header file do:

namespace TRIALS {
    extern thread_local int a;
}

And in one of the .cpp:

thread_local int TRIALS::a = -1;

In C++17, you can make the variable inline to avoid having to provide its definition in a .cpp:

namespace TRIALS {
    inline thread_local int a = -1;
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271