3

The following code fragment from C++ reference illustrates how notify_all_at_thread_exit can be used to avoid accessing data that depends on thread locals while those thread locals are in the process of being destructed:

    #include <mutex>
    #include <thread>
    #include <condition_variable>
     
    #include <cassert>
    #include <string>
     
    std::mutex m;
    std::condition_variable cv;
     
    bool ready = false;
    std::string result; // some arbitrary type
     
    void thread_func()
    {
        thread_local std::string thread_local_data = "42";
     
        std::unique_lock<std::mutex> lk(m);
     
        // assign a value to result using thread_local data
        result = thread_local_data;
        ready = true;
     
        std::notify_all_at_thread_exit(cv, std::move(lk));
     
    }   // 1. destroy thread_locals;
        // 2. unlock mutex;
        // 3. notify cv.
     
    int main()
    {
        std::thread t(thread_func);
        t.detach();
     
        // do other work
        // ...
     
        // wait for the detached thread
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{ return ready; });
     
        // result is ready and thread_local destructors have finished, no UB
        assert(result == "42");
    }

Since the result variable stores a stand-alone copy of thread_local_data, I can't see why accessing result while destroying thread-local data might be a problem here. Would it still work reliably, if std::notify_all_at_thread_exit were replaced with lk.unlock() followed by cv.notify_all()?

#include <mutex>
#include <thread>
#include <condition_variable>

#include <cassert>
#include <string>

std::mutex m;
std::condition_variable cv;

bool ready = false;
std::string result; // some arbitrary type

void thread_func()
{
    thread_local std::string thread_local_data = "42";

    std::unique_lock<std::mutex> lk(m);

    // assign a value to result using thread_local data
    result = thread_local_data;
    ready = true;

    //std::notify_all_at_thread_exit(cv, std::move(lk));
    lk.unlock();
    cv.notify_all();

}  

int main()
{
    std::thread t(thread_func);
    t.detach();

    // do other work
    // ...

    // wait for the detached thread
    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk, [] { return ready; });

    // result is ready and thread_local destructors have finished, no UB
    assert(result == "42");
}

I would appreciate an example of the situation when we really need to know that all thread_local destructors have finished.

mentalmushroom
  • 2,261
  • 1
  • 26
  • 34

0 Answers0