C++11 thread_local destructor behaviour
Asked Answered
M

2

5

I have following situation: In a header "test.hpp" I define:

class ObjectA {
    public:
        ObjectA();
        ~ObjectA();
        static ObjectA & get_A();
};
class ObjectB {
    public:
        ~ObjectB();
        static ObjectB & get_B();
        void do_cleanup();
};

And in separate compilation units I implement ObjectB:

#include "test.hpp"
#include <iostream>
ObjectB::~ObjectB() {
    std::cout<<"ObjectB dtor"<<std::endl;
}
ObjectB & ObjectB::get_B() {
    thread_local ObjectB b_instance;
    return b_instance;
}
void ObjectB::do_cleanup() {
    std::cout<<"Clearing up B garbage..."<<std::endl;
}

ObjectA:

#include "test.hpp"
#include <iostream>
ObjectA::ObjectA() {
    ObjectB::get_B(); <--dummy call to initialize thread_local ObjectB;
}
ObjectA::~ObjectA() {
     std::cout<<"ObjectA dtor"<<std::endl;
     ObjectB::get_B().do_cleanup(); // <-- is this undefined behaviour??
}
ObjectA & ObjectA::get_A() {
     thread_local ObjectA a_instance;
     return a_instance;
}

And finally a test main():

#include <thread>
#include "test.hpp"
int main() {
    std::thread check([](){
    ObjectA::get_A(); //<--dummy call just to initialize thread_local object.
    });
    check.join();
    return 0;
}

Is above program well behaved or is accessing objectB, which has thread_local storage from ObjectA destructor which also has thread_local storage undefined behaviour? If so, why is it breaking and how do I fix it?

most related question I found

[edit, @Soonts answer]

In real use case, the A class is template, a quite complex one and B class is just large. A objects hold references to B's using shared_ptr<> and B's thread_locals are accessed as-needed basis. (A's are constructed in main thread and passed to workers) ObjectB::get_B() thus may not be called by worker threads before ObjectA::get_A() gets called.

Marva answered 21/8, 2018 at 17:40 Comment(2)
Have you checked your compiler supports thread locals? Not all compilers have caught up to that part of the standard. But the ordering looks correct. Your use of B in the constructor of A makes sure it is live before A. So A will be destroued first. Which calls B cleanup then B will be destroyed. This should all happen within the scope of the same thread.Bojorquez
Note: Compiles and runs correctly on mac using LLVM version 8.0.0Bojorquez
O
6

The spec says couple of things about lifetimes:

Storage class specifiers

thread storage duration. The storage for the object is allocated when the thread begins and deallocated when the thread ends. Each thread has its own instance of the object.

Termination

If the completion of the constructor or dynamic initialization of an object with thread storage duration is sequenced before that of another, the completion of the destructor of the second is sequenced before the initiation of the destructor of the first.

Now back to your code.

You construct A, in the constructor you construct B. Therefore, the completion of the B constructor happens before the completion of the A constructor. According to the above, when thread is about to quit, it will first destroy A then B. According to the letter of the spec your code is OK.

Practically speaking, I’m not sure C++ compilers implement spec to that level of detail. If I were writing that code, I wouldn’t use thread_local objects this way. Instead, I would put B in non-static field of A. It’s just simpler and IMO more reliable than relying on such nuances of the language standard.

Octroi answered 21/8, 2018 at 18:24 Comment(1)
OK, thank you for clarification. I tough I hit thread_local version of static object destruction hell. (undefined order in which two compilation unit's static objects are destroyed)Marva
N
0

There is a bug in MSVC compiler regarding thread_local and object destruction.

And Microsoft VC++ team doesn't have a clue how to fix it.

Due to the way the thread pool works, the threads created by your program continue to live even after program termination. So, there is no guarantee that the thread_local object destructor gets called.

The issue is reported to VC++ team in below link

https://developercommunity.visualstudio.com/t/thread-local/798234

Neighboring answered 6/7, 2023 at 23:36 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.