• thread_local question...

    From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Wed Oct 9 19:02:15 2024
    From Newsgroup: comp.lang.c++

    How can I get access to self in my ct_foo() function below? Am I
    misusing thread_local here? I want self to be able to be accessible from
    any function that my ct_thread() function calls. It feels like I am
    doing something wrong here. However, I am getting correct ctor's and
    dtor's, I am not sure how to access self from ct_foo() called from ct_thread()?

    I must be missing something obvious.

    First of all can you compile and run it?
    _______________________________
    #include <iostream>
    #include <functional>
    #include <thread>
    #include <atomic>
    #include <mutex>


    #define CT_THREADS 5


    struct ct_shared
    {
    std::mutex m_cout_lock;
    };


    struct ct_per_thread
    {
    ct_shared& m_shared;
    unsigned long m_id;

    ct_per_thread(
    ct_shared& shared,
    unsigned long id

    ): m_shared(shared),
    m_id(id)
    {
    {
    std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
    std::cout << "ct_per_thread::ct_per_thread(" << m_id << ")"
    << std::endl;
    }
    }

    ~ct_per_thread()
    {
    {
    std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
    std::cout << "ct_per_thread::~ct_per_thread(" << m_id <<
    ")" << std::endl;
    }
    }
    };



    void
    ct_foo()
    {
    // Ummm, how to get at self?
    }


    void
    ct_thread(
    ct_shared& shared,
    unsigned long id
    ) {
    // Well, self should be per thread...
    thread_local ct_per_thread self(shared, id);

    ct_foo();
    }


    int
    main()
    {
    std::cout << "ct_plot_pre_alpha... Testing 123! :^)\n\n";
    std::cout << "_____________________________________________" << std::endl;

    {
    ct_shared shared;

    {
    std::thread threads[CT_THREADS];

    std::cout << "launching " << CT_THREADS << " threads..." << std::endl;

    for (unsigned long i = 0; i < CT_THREADS; ++i)
    {
    threads[i] = std::thread(ct_thread, std::ref(shared), i);
    }

    for (unsigned long i = 0; i < CT_THREADS; ++i)
    {
    threads[i].join();
    }
    }
    }

    std::cout << "_____________________________________________\n";
    std::cout << "complete!\n";

    return 0;
    }
    _______________________________


    Here is one of the outputs I get:
    ____________
    ct_plot_pre_alpha... Testing 123! :^)

    _____________________________________________
    launching 5 threads...
    ct_per_thread::ct_per_thread(0)
    ct_per_thread::ct_per_thread(2)
    ct_per_thread::ct_per_thread(3)
    ct_per_thread::ct_per_thread(1)
    ct_per_thread::ct_per_thread(4)
    ct_per_thread::~ct_per_thread(0)
    ct_per_thread::~ct_per_thread(2)
    ct_per_thread::~ct_per_thread(3)
    ct_per_thread::~ct_per_thread(1)
    ct_per_thread::~ct_per_thread(4)
    _____________________________________________
    complete!
    ____________


    Still, I think the tss_create() is better. Perhaps because it's more in
    line with PThreads... ;^)

    Any thoughts?
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Wed Oct 9 19:08:28 2024
    From Newsgroup: comp.lang.c++

    On 10/9/2024 7:02 PM, Chris M. Thomasson wrote:
    How can I get access to self in my ct_foo() function below? Am I
    misusing thread_local here? I want self to be able to be accessible from
    any function that my ct_thread() function calls. It feels like I am
    doing something wrong here. However, I am getting correct ctor's and
    dtor's, I am not sure how to access self from ct_foo() called from ct_thread()?

    I must be missing something obvious.

    First of all can you compile and run it?
    _______________________________
    [...]

    Still, I think the tss_create() is better. Perhaps because it's more in
    line with PThreads... ;^)

    Any thoughts?

    I think I could store a per-thread pointer to it in a global via
    thread_local. Humm...

    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Andrey Tarasevich@andreytarasevich@hotmail.com to comp.lang.c++ on Wed Oct 9 19:33:55 2024
    From Newsgroup: comp.lang.c++

    On 10/09/24 7:02 PM, Chris M. Thomasson wrote:

    void
    ct_thread(
        ct_shared& shared,
        unsigned long id
    ) {
        // Well, self should be per thread...
        thread_local ct_per_thread self(shared, id);

        ct_foo();
    }


    Um... Declaring a `thread_local` variable in block scope can serve only
    one purpose: to _hide_ the variable, to prevent anyone else from
    accessing it directly. `thread_local` is somewhat similar to `static` in single-threaded applications: the variable retains its value even when
    you exit and re-enter the scope. In all other respects the variable
    itself remain invisible to the outside scopes, which is the whole
    purpose of declaring it in block scope.

    This is exactly what you did: you declared a variable as `thread_local`
    in block scope. Which means it will retain its value when you exit and re-enter the scope, but (as opposed to `static`) it is going to be a per-thread variable. Since you declared it locally, it implies that you _wanted_ to make it inaccessible from other scopes. Why are asking "how
    to get at self?" then?

    The only way to access such variable from other scopes is to access it indirectly: through a reference/pointer that you create in advance
    (where the variable is visible) and then somehow hand that
    reference/pointer over to other scopes. If you don't what to fiddle with indirect access, then declare your variable as `thread_local` in
    namespace scope. That way you will easily be able to get at it from
    other functions.

    However, the real question here is: why you are even declaring your
    variable as `thread_local`? A regular local variable inside a thread
    function is already as "thread local" as it can possibly get. The only
    thing that `thread_local` will give you is the retention of the old
    value between calls. Do you really need that?
    --
    Best regards,
    Andrey
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Andrey Tarasevich@andreytarasevich@hotmail.com to comp.lang.c++ on Wed Oct 9 19:40:55 2024
    From Newsgroup: comp.lang.c++

    On 10/09/24 7:33 PM, Andrey Tarasevich wrote:
    On 10/09/24 7:02 PM, Chris M. Thomasson wrote:

    void
    ct_thread(
         ct_shared& shared,
         unsigned long id
    ) {
         // Well, self should be per thread...
         thread_local ct_per_thread self(shared, id);

         ct_foo();
    }



    However, the real question here is: why you are even declaring your
    variable as `thread_local`? A regular local variable inside a thread function is already as "thread local" as it can possibly get. The only
    thing that `thread_local` will give you is the retention of the old
    value between calls. Do you really need that?


    An additional remark:

    The "retention of the old value between calls" would apply if you
    declared a variable like that in some nested function invoked multiple
    times from the top-level thread function.

    But in your example the variable is declared in the top-level thread
    function itself. In that case there will be no "retention of the old
    value between calls" simply because once the thread function ends, the
    thread ends as well, and all its `thread_local` variables die anyway.
    So, your declaration does not seem to make any practical sense.
    --
    Best regards,
    Andrey
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Wed Oct 9 20:08:55 2024
    From Newsgroup: comp.lang.c++

    On 10/9/2024 7:40 PM, Andrey Tarasevich wrote:
    On 10/09/24 7:33 PM, Andrey Tarasevich wrote:
    On 10/09/24 7:02 PM, Chris M. Thomasson wrote:

    void
    ct_thread(
         ct_shared& shared,
         unsigned long id
    ) {
         // Well, self should be per thread...
         thread_local ct_per_thread self(shared, id);

         ct_foo();
    }



    However, the real question here is: why you are even declaring your
    variable as `thread_local`? A regular local variable inside a thread
    function is already as "thread local" as it can possibly get. The only
    thing that `thread_local` will give you is the retention of the old
    value between calls. Do you really need that?


    An additional remark:

    The "retention of the old value between calls" would apply if you
    declared a variable like that in some nested function invoked multiple
    times from the top-level thread function.

    But in your example the variable is declared in the top-level thread function itself. In that case there will be no "retention of the old
    value between calls" simply because once the thread function ends, the thread ends as well, and all its `thread_local` variables die anyway.
    So, your declaration does not seem to make any practical sense.


    How can I make my thread_local ct_per_thread self(shared, id) local to
    all threads? I am missing something obvious, I think... Shit. This is
    rather easily handled with tss_create or pthread_get/setspecific. I am
    missing something here, big time. Sorry!, shit.

    Well, I am not sure how to pass per thread information into a
    thread_local _object_ that is global wrt a per thread ctor. I am so used
    to the pthread way of doing things where the pthread_key_t would be
    global... Actually, the C way now. tss_create and friends. Or the
    tss_key in:

    https://en.cppreference.com/w/c/thread/tss_create

    Perhaps a global of:

    thread_local ct_per_thread* g_tss = nullptr; // ?

    ? Ahh shit. I just need to try it out. I would want the tss dtors to be
    called on a per thread basis, way deep in thread shutdown...
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Wed Oct 9 20:19:11 2024
    From Newsgroup: comp.lang.c++

    On 10/9/2024 7:33 PM, Andrey Tarasevich wrote:
    On 10/09/24 7:02 PM, Chris M. Thomasson wrote:

    void
    ct_thread(
         ct_shared& shared,
         unsigned long id
    ) {
         // Well, self should be per thread...
         thread_local ct_per_thread self(shared, id);

         ct_foo();
    }


    Um... Declaring a `thread_local` variable in block scope can serve only
    one purpose: to _hide_ the variable, to prevent anyone else from
    accessing it directly. `thread_local` is somewhat similar to `static` in single-threaded applications: the variable retains its value even when
    you exit and re-enter the scope. In all other respects the variable
    itself remain invisible to the outside scopes, which is the whole
    purpose of declaring it in block scope.[...]

    Okay, well, is this any better, or worse!

    ;^o


    _________________________
    #include <iostream>
    #include <functional>
    #include <thread>
    #include <atomic>
    #include <mutex>


    #define CT_THREADS 5


    struct ct_shared
    {
    std::mutex m_cout_lock;
    };


    struct ct_per_thread
    {
    ct_shared& m_shared;
    unsigned long m_id;

    ct_per_thread(
    ct_shared& shared,
    unsigned long id

    ): m_shared(shared),
    m_id(id)
    {
    {
    std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
    std::cout << "ct_per_thread::ct_per_thread(" << m_id << ")"
    << std::endl;
    }
    }

    ~ct_per_thread()
    {
    {
    std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
    std::cout << "ct_per_thread::~ct_per_thread(" << m_id <<
    ")" << std::endl;
    }
    }
    };



    thread_local ct_per_thread* g_per_thread = nullptr;



    void
    ct_foo()
    {
    // Okay, what about this shit!
    {
    std::unique_lock<std::mutex> lock(g_per_thread->m_shared.m_cout_lock);
    std::cout << "ct_foo(" << g_per_thread->m_id << ")" << std::endl;
    }
    }


    void
    ct_thread(
    ct_shared& shared,
    unsigned long id
    ) {
    ct_per_thread self(shared, id);

    g_per_thread = &self;

    ct_foo();
    }


    int
    main()
    {
    std::cout << "ct_plot_pre_alpha... Testing 123! :^)\n\n";
    std::cout << "_____________________________________________" << std::endl;

    {
    ct_shared shared;

    {
    std::thread threads[CT_THREADS];

    std::cout << "launching " << CT_THREADS << " threads..." << std::endl;

    for (unsigned long i = 0; i < CT_THREADS; ++i)
    {
    threads[i] = std::thread(ct_thread, std::ref(shared), i);
    }

    for (unsigned long i = 0; i < CT_THREADS; ++i)
    {
    threads[i].join();
    }
    }
    }

    std::cout << "_____________________________________________\n";
    std::cout << "complete!\n";

    return 0;
    }
    _________________________
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From jseigh@jseigh_es00@xemaps.com to comp.lang.c++ on Thu Oct 10 07:52:53 2024
    From Newsgroup: comp.lang.c++

    On 10/9/24 22:02, Chris M. Thomasson wrote:
    How can I get access to self in my ct_foo() function below? Am I
    misusing thread_local here? I want self to be able to be accessible from
    any function that my ct_thread() function calls. It feels like I am
    doing something wrong here. However, I am getting correct ctor's and
    dtor's, I am not sure how to access self from ct_foo() called from ct_thread()?


    std::thread::id id = std::this_thread::get_id();

    C tss uses a thread local array I think but I don't know
    it gets notification on individual thread exits so it
    can run the dtor. C++ has a cvar notify all on thread
    exits.

    I have a different hack which I won't get around to
    until I need it.

    Joe Seigh

    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Thu Oct 10 12:04:14 2024
    From Newsgroup: comp.lang.c++

    On 10/10/2024 4:52 AM, jseigh wrote:
    On 10/9/24 22:02, Chris M. Thomasson wrote:
    How can I get access to self in my ct_foo() function below? Am I
    misusing thread_local here? I want self to be able to be accessible
    from any function that my ct_thread() function calls. It feels like I
    am doing something wrong here. However, I am getting correct ctor's
    and dtor's, I am not sure how to access self from ct_foo() called from
    ct_thread()?


    std::thread::id id = std::this_thread::get_id();

    C tss uses a thread local array I think but I don't know
    it gets notification on individual thread exits so it
    can run the dtor.  C++ has a cvar notify all on thread
    exits.

    I have a different hack which I won't get around to
    until I need it.

    I still don't know how C++ calls dtors wrt per-thread objects via thread_local. My hack would be to use tss_create and be done with it.

    However, this "works", but the only reason the dtor is being called has nothing to do with thread_local. I resorted to using thread_local for a pointer to a local per_thread object on the threads stack. It's dtor
    means the thread exited. However, this is a lot different than
    tss_create or pthread_key_create. Iirc, those dtors get called a lot
    deeper in thread shutdown.

    _______________________
    thread_local ct_per_thread* g_per_thread = nullptr;

    void
    ct_foo()
    {
    // Okay, what about this shit!
    {
    std::unique_lock<std::mutex> lock(g_per_thread->m_shared.m_cout_lock);
    std::cout << "ct_foo(" << g_per_thread->m_id << ")" << std::endl;
    }
    }


    void
    ct_thread(
    ct_shared& shared,
    unsigned long id
    ) {
    ct_per_thread self(shared, id);

    g_per_thread = &self;

    ct_foo();
    }
    _______________________


    I guess that is a hack.
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From wij@wyniijj5@gmail.com to comp.lang.c++ on Fri Oct 11 03:20:42 2024
    From Newsgroup: comp.lang.c++

    On Thu, 2024-10-10 at 12:04 -0700, Chris M. Thomasson wrote:
    On 10/10/2024 4:52 AM, jseigh wrote:
    On 10/9/24 22:02, Chris M. Thomasson wrote:
    How can I get access to self in my ct_foo() function below? Am I misusing thread_local here? I want self to be able to be accessible
    from any function that my ct_thread() function calls. It feels like I
    am doing something wrong here. However, I am getting correct ctor's
    and dtor's, I am not sure how to access self from ct_foo() called from ct_thread()?


    std::thread::id id = std::this_thread::get_id();

    C tss uses a thread local array I think but I don't know
    it gets notification on individual thread exits so it
    can run the dtor.  C++ has a cvar notify all on thread
    exits.

    I have a different hack which I won't get around to
    until I need it.

    I still don't know how C++ calls dtors wrt per-thread objects via thread_local. My hack would be to use tss_create and be done with it.
    That's why thread in libwy (RAII) is implemented as a class, 'thread' is an overload
    virtual function in the thread class Wy::Thread (inspired by old day Qt thread). And,
    'thread local' mostly is data member of the Wy::PThread. Not many app. really need
    'thread local' (Other threads can access the 'local thread data').
    However, this "works", but the only reason the dtor is being called has nothing to do with thread_local. I resorted to using thread_local for a pointer to a local per_thread object on the threads stack. It's dtor
    means the thread exited. However, this is a lot different than
    tss_create or pthread_key_create. Iirc, those dtors get called a lot
    deeper in thread shutdown.
    Yes, RRAD is a problem of multi-threaded program.
    _______________________
    thread_local ct_per_thread* g_per_thread = nullptr;

    void
    ct_foo()
    {
         // Okay, what about this shit!
         {
             std::unique_lock<std::mutex> lock(g_per_thread->m_shared.m_cout_lock);
             std::cout << "ct_foo(" << g_per_thread->m_id << ")" << std::endl;
         }
    }


    void
    ct_thread(
         ct_shared& shared,
         unsigned long id
    ) {
         ct_per_thread self(shared, id);

         g_per_thread = &self;

         ct_foo();
    }
    _______________________


    I guess that is a hack.
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From jseigh@jseigh_es00@xemaps.com to comp.lang.c++ on Thu Oct 10 15:57:42 2024
    From Newsgroup: comp.lang.c++

    On 10/10/24 15:04, Chris M. Thomasson wrote:
    On 10/10/2024 4:52 AM, jseigh wrote:
    On 10/9/24 22:02, Chris M. Thomasson wrote:
    How can I get access to self in my ct_foo() function below? Am I
    misusing thread_local here? I want self to be able to be accessible
    from any function that my ct_thread() function calls. It feels like I
    am doing something wrong here. However, I am getting correct ctor's
    and dtor's, I am not sure how to access self from ct_foo() called
    from ct_thread()?


    std::thread::id id = std::this_thread::get_id();

    C tss uses a thread local array I think but I don't know
    it gets notification on individual thread exits so it
    can run the dtor.  C++ has a cvar notify all on thread
    exits.

    I have a different hack which I won't get around to
    until I need it.

    I still don't know how C++ calls dtors wrt per-thread objects via thread_local. My hack would be to use tss_create and be done with it.


    I got some stack traces from C vs C++ threads (not the main threads
    which have identical stack traces).

    C
    #4 0x00000000004011c6 in test ()
    #5 0x00007ff68a489be1 in start_thread (arg=<optimized out>) at pthread_create.c:440
    #6 0x00007ff68a50ed40 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

    C++
    #4 0x00000000004012b6 in test(void*) ()
    #5 0x00007fa23f4dbad4 in std::execute_native_thread_routine
    (__p=0x173ceb0) at ../../../../../libstdc++-v3/src/c++11/thread.cc:82
    #6 0x00007fa23f089d22 in start_thread (arg=<optimized out>) at pthread_create.c:443
    #7 0x00007fa23f10ed40 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

    So the tss uses the pthread stuff and C++ uses std::execute_native_thread_routine on top of that.

    C and C++ main
    #4 0x0000000000401056 in main ()
    #5 0x00007fae98e295d0 in __libc_start_call_main
    (main=main@entry=0x401050 <main>, argc=argc@entry=1,
    argv=argv@entry=0x7ffff84f1d18) at ../sysdeps/nptl/libc_start_call_main.h:58
    #6 0x00007fae98e29680 in __libc_start_main_impl (main=0x401050 <main>, argc=1, argv=0x7ffff84f1d18, init=<optimized out>,
    fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffff84f1d08) at ../csu/libc-start.c:389
    #7 0x0000000000401085 in _start ()

    No extra c++ magic sauce there that I can see.

    I wonder how many people realize a thread's entry point isn't
    necessarily the first thing that's called.

    Joe Seigh



    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From jseigh@jseigh_es00@xemaps.com to comp.lang.c++ on Fri Oct 11 09:50:13 2024
    From Newsgroup: comp.lang.c++

    On 10/10/24 15:04, Chris M. Thomasson wrote:
    On 10/10/2024 4:52 AM, jseigh wrote:
    On 10/9/24 22:02, Chris M. Thomasson wrote:
    How can I get access to self in my ct_foo() function below? Am I
    misusing thread_local here? I want self to be able to be accessible
    from any function that my ct_thread() function calls. It feels like I
    am doing something wrong here. However, I am getting correct ctor's
    and dtor's, I am not sure how to access self from ct_foo() called
    from ct_thread()?


    std::thread::id id = std::this_thread::get_id();

    C tss uses a thread local array I think but I don't know
    it gets notification on individual thread exits so it
    can run the dtor.  C++ has a cvar notify all on thread
    exits.

    I have a different hack which I won't get around to
    until I need it.

    I still don't know how C++ calls dtors wrt per-thread objects via thread_local. My hack would be to use tss_create and be done with it.


    Ok, it looks like they are using libc atexit function (stdlib.h) to
    invoke tls cleanup on thread exit.

    Joe Seigh

    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Fri Oct 11 12:21:09 2024
    From Newsgroup: comp.lang.c++

    On 10/11/2024 6:50 AM, jseigh wrote:
    On 10/10/24 15:04, Chris M. Thomasson wrote:
    On 10/10/2024 4:52 AM, jseigh wrote:
    On 10/9/24 22:02, Chris M. Thomasson wrote:
    How can I get access to self in my ct_foo() function below? Am I
    misusing thread_local here? I want self to be able to be accessible
    from any function that my ct_thread() function calls. It feels like
    I am doing something wrong here. However, I am getting correct
    ctor's and dtor's, I am not sure how to access self from ct_foo()
    called from ct_thread()?


    std::thread::id id = std::this_thread::get_id();

    C tss uses a thread local array I think but I don't know
    it gets notification on individual thread exits so it
    can run the dtor.  C++ has a cvar notify all on thread
    exits.

    I have a different hack which I won't get around to
    until I need it.

    I still don't know how C++ calls dtors wrt per-thread objects via
    thread_local. My hack would be to use tss_create and be done with it.


    Ok, it looks like they are using libc atexit function (stdlib.h) to
    invoke tls cleanup on thread exit.

    Thanks for all of the info Joe! I have an idea that might allow me to
    get a dtor called from thread_local in my test code. I just need to try
    it. Wrt:
    _________________________________

    thread_local ct_per_thread* g_per_thread = nullptr;


    void
    ct_foo()
    {
    // Okay, what about this shit!
    {
    std::unique_lock<std::mutex> lock(g_per_thread->m_shared.m_cout_lock);
    std::cout << "ct_foo(" << g_per_thread->m_id << ")" << std::endl;
    }
    }


    void
    ct_thread(
    ct_shared& shared,
    unsigned long id
    ) {
    ct_per_thread self(shared, id);

    g_per_thread = &self;

    ct_foo();
    }
    _________________________________


    Well, what if I made self in ct_thread thread_local as well? That should
    call self's dtor deeper down the pipe, so to speak. Humm... It should
    work fine. Then any function that ct_thread calls would be able to
    access self via g_per_thread...

    ;^)
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Fri Oct 11 13:36:03 2024
    From Newsgroup: comp.lang.c++

    On 10/10/2024 12:57 PM, jseigh wrote:
    On 10/10/24 15:04, Chris M. Thomasson wrote:
    [...]
    I wonder how many people realize a thread's entry point isn't
    necessarily the first thing that's called.

    No shit! Good one. :^)

    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From jseigh@jseigh_es00@xemaps.com to comp.lang.c++ on Fri Oct 11 17:05:33 2024
    From Newsgroup: comp.lang.c++

    On 10/11/24 09:50, jseigh wrote:
    On 10/10/24 15:04, Chris M. Thomasson wrote:
    On 10/10/2024 4:52 AM, jseigh wrote:
    On 10/9/24 22:02, Chris M. Thomasson wrote:
    How can I get access to self in my ct_foo() function below? Am I
    misusing thread_local here? I want self to be able to be accessible
    from any function that my ct_thread() function calls. It feels like
    I am doing something wrong here. However, I am getting correct
    ctor's and dtor's, I am not sure how to access self from ct_foo()
    called from ct_thread()?


    std::thread::id id = std::this_thread::get_id();

    C tss uses a thread local array I think but I don't know
    it gets notification on individual thread exits so it
    can run the dtor.  C++ has a cvar notify all on thread
    exits.

    I have a different hack which I won't get around to
    until I need it.

    I still don't know how C++ calls dtors wrt per-thread objects via
    thread_local. My hack would be to use tss_create and be done with it.


    Ok, it looks like they are using libc atexit function (stdlib.h) to
    invoke tls cleanup on thread exit.

    Correction. atexit is for process exit not thread exit. So those
    start_ functions are where they are doing the exit handling.

    Joe Seigh

    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Fri Oct 11 22:53:30 2024
    From Newsgroup: comp.lang.c++

    On 10/11/2024 12:21 PM, Chris M. Thomasson wrote:
    On 10/11/2024 6:50 AM, jseigh wrote:
    On 10/10/24 15:04, Chris M. Thomasson wrote:
    On 10/10/2024 4:52 AM, jseigh wrote:
    On 10/9/24 22:02, Chris M. Thomasson wrote:
    How can I get access to self in my ct_foo() function below? Am I
    misusing thread_local here? I want self to be able to be accessible >>>>> from any function that my ct_thread() function calls. It feels like >>>>> I am doing something wrong here. However, I am getting correct
    ctor's and dtor's, I am not sure how to access self from ct_foo()
    called from ct_thread()?


    std::thread::id id = std::this_thread::get_id();

    C tss uses a thread local array I think but I don't know
    it gets notification on individual thread exits so it
    can run the dtor.  C++ has a cvar notify all on thread
    exits.

    I have a different hack which I won't get around to
    until I need it.

    I still don't know how C++ calls dtors wrt per-thread objects via
    thread_local. My hack would be to use tss_create and be done with it.


    Ok, it looks like they are using libc atexit function (stdlib.h) to
    invoke tls cleanup on thread exit.

    Thanks for all of the info Joe! I have an idea that might allow me to
    get a dtor called from thread_local in my test code. I just need to try
    it. Wrt:
    _________________________________

    thread_local ct_per_thread* g_per_thread = nullptr;


    void
    ct_foo()
    {
        // Okay, what about this shit!
        {
            std::unique_lock<std::mutex> lock(g_per_thread-
    m_shared.m_cout_lock);
            std::cout << "ct_foo(" << g_per_thread->m_id << ")" << std::endl;
        }
    }


    void
    ct_thread(
        ct_shared& shared,
        unsigned long id
    ) {
        ct_per_thread self(shared, id);

        g_per_thread = &self;

        ct_foo();
    }
    _________________________________


    Well, what if I made self in ct_thread thread_local as well? That should call self's dtor deeper down the pipe, so to speak. Humm... It should
    work fine. Then any function that ct_thread calls would be able to
    access self via g_per_thread...

    ;^)

    Humm... It would still not be "dynamic" like the pthread and/or
    tss_create version.
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Fri Oct 11 22:56:21 2024
    From Newsgroup: comp.lang.c++

    On 10/11/2024 10:53 PM, Chris M. Thomasson wrote:
    [...]

    Humm... It would still not be "dynamic" like the pthread and/or
    tss_create version.

    It might be good enough for my purposes...
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Sat Oct 12 13:29:31 2024
    From Newsgroup: comp.lang.c++

    [...]

    Check this out... Does it work for you? The ct_per_thread dtors should
    be getting called deeper in thread shutdown...

    Take note of the following function:
    _______________________
    void
    ct_thread(
    ct_shared& shared,
    unsigned long id
    ) {
    {
    thread_local ct_per_thread self(shared, id);

    g_per_thread = &self;
    }

    ct_foo();
    }
    _______________________


    Here is my crude code:
    _______________________________________
    #include <iostream>
    #include <functional>
    #include <thread>
    #include <atomic>
    #include <mutex>


    #define CT_THREADS 5


    struct ct_shared
    {
    std::mutex m_cout_lock;
    };


    struct ct_per_thread
    {
    ct_shared& m_shared;
    unsigned long m_id;

    ct_per_thread(
    ct_shared& shared,
    unsigned long id

    ): m_shared(shared),
    m_id(id)
    {
    {
    std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
    std::cout << "ct_per_thread::ct_per_thread(" << m_id << ")"
    << std::endl;
    }
    }

    ~ct_per_thread()
    {
    {
    std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
    std::cout << "ct_per_thread::~ct_per_thread(" << m_id <<
    ")" << std::endl;
    }
    }
    };



    thread_local ct_per_thread* g_per_thread = nullptr;



    void
    ct_foo()
    {
    // Okay, what about this shit!
    {
    std::unique_lock<std::mutex> lock(g_per_thread->m_shared.m_cout_lock);
    std::cout << "ct_foo(" << g_per_thread->m_id << ")" << std::endl;
    }
    }


    void
    ct_thread(
    ct_shared& shared,
    unsigned long id
    ) {
    {
    thread_local ct_per_thread self(shared, id);

    g_per_thread = &self;
    }

    ct_foo();
    }


    int
    main()
    {
    std::cout << "ct_plot_pre_alpha... Testing 123! :^)\n\n";
    std::cout << "_____________________________________________" << std::endl;

    {
    ct_shared shared;

    {
    std::thread threads[CT_THREADS];

    std::cout << "launching " << CT_THREADS << " threads..." << std::endl;

    for (unsigned long i = 0; i < CT_THREADS; ++i)
    {
    threads[i] = std::thread(ct_thread, std::ref(shared), i);
    }

    for (unsigned long i = 0; i < CT_THREADS; ++i)
    {
    threads[i].join();
    }
    }
    }

    std::cout << "_____________________________________________\n";
    std::cout << "complete!\n";

    return 0;
    }
    _______________________________________



    Any thoughts?
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Sat Oct 12 13:43:18 2024
    From Newsgroup: comp.lang.c++

    On 10/12/2024 1:29 PM, Chris M. Thomasson wrote:
    [...]

    Check this out... Does it work for you? The ct_per_thread dtors should
    be getting called deeper in thread shutdown...
    [...]
        ~ct_per_thread()
        {
            {
                std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
                std::cout << "ct_per_thread::~ct_per_thread(" << m_id <<
    ")" << std::endl;
            }
        }
    };[...]

    The call stack for ct_per_thread::~ct_per_thread() is:

    ct_plot_pre_alpha.exe!ct_per_thread::~ct_per_thread() Line 39 C++
    ct_plot_pre_alpha.exe!`ct_thread'::`3'::`dynamic atexit destructor
    for 'self''() C++
    ct_plot_pre_alpha.exe!__dyn_tls_dtor(void * __formal, const unsigned long dwReason, void * __formal) Line 122 C++
    ntdll.dll!00007ffbed20bfca() Unknown
    ntdll.dll!00007ffbed1b8b5f() Unknown
    ntdll.dll!00007ffbed1b97e9() Unknown
    ntdll.dll!00007ffbed1b9428() Unknown
    ntdll.dll!00007ffbed1eaf7e() Unknown
    KernelBase.dll!00007ffbea68e1ca() Unknown
    ucrtbased.dll!00007ffb83e932cb() Unknown
    ucrtbased.dll!00007ffb83e93931() Unknown
    ucrtbased.dll!00007ffb83e93017() Unknown
    kernel32.dll!00007ffbec15257d() Unknown
    ntdll.dll!00007ffbed1eaf08() Unknown


    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Sat Oct 12 13:44:09 2024
    From Newsgroup: comp.lang.c++

    On 10/11/2024 2:05 PM, jseigh wrote:
    On 10/11/24 09:50, jseigh wrote:
    On 10/10/24 15:04, Chris M. Thomasson wrote:
    On 10/10/2024 4:52 AM, jseigh wrote:
    On 10/9/24 22:02, Chris M. Thomasson wrote:
    How can I get access to self in my ct_foo() function below? Am I
    misusing thread_local here? I want self to be able to be accessible >>>>> from any function that my ct_thread() function calls. It feels like >>>>> I am doing something wrong here. However, I am getting correct
    ctor's and dtor's, I am not sure how to access self from ct_foo()
    called from ct_thread()?


    std::thread::id id = std::this_thread::get_id();

    C tss uses a thread local array I think but I don't know
    it gets notification on individual thread exits so it
    can run the dtor.  C++ has a cvar notify all on thread
    exits.

    I have a different hack which I won't get around to
    until I need it.

    I still don't know how C++ calls dtors wrt per-thread objects via
    thread_local. My hack would be to use tss_create and be done with it.


    Ok, it looks like they are using libc atexit function (stdlib.h) to
    invoke tls cleanup on thread exit.

    Correction.  atexit is for process exit not thread exit.  So those
    start_ functions are where they are doing the exit handling.

    Well, here is a call stack for my most recent try wrt tss dtors in pure C++:

    ct_plot_pre_alpha.exe!ct_per_thread::~ct_per_thread() Line 39 C++
    ct_plot_pre_alpha.exe!`ct_thread'::`3'::`dynamic atexit destructor
    for 'self''() C++
    ct_plot_pre_alpha.exe!__dyn_tls_dtor(void * __formal, const unsigned long dwReason, void * __formal) Line 122 C++
    ntdll.dll!00007ffbed20bfca() Unknown
    ntdll.dll!00007ffbed1b8b5f() Unknown
    ntdll.dll!00007ffbed1b97e9() Unknown
    ntdll.dll!00007ffbed1b9428() Unknown
    ntdll.dll!00007ffbed1eaf7e() Unknown
    KernelBase.dll!00007ffbea68e1ca() Unknown
    ucrtbased.dll!00007ffb83e932cb() Unknown
    ucrtbased.dll!00007ffb83e93931() Unknown
    ucrtbased.dll!00007ffb83e93017() Unknown
    kernel32.dll!00007ffbec15257d() Unknown
    ntdll.dll!00007ffbed1eaf08() Unknown


    atexit is in there.

    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Sat Oct 12 13:46:20 2024
    From Newsgroup: comp.lang.c++

    On 10/12/2024 1:44 PM, Chris M. Thomasson wrote:
    On 10/11/2024 2:05 PM, jseigh wrote:
    On 10/11/24 09:50, jseigh wrote:
    On 10/10/24 15:04, Chris M. Thomasson wrote:
    On 10/10/2024 4:52 AM, jseigh wrote:
    On 10/9/24 22:02, Chris M. Thomasson wrote:
    How can I get access to self in my ct_foo() function below? Am I
    misusing thread_local here? I want self to be able to be
    accessible from any function that my ct_thread() function calls.
    It feels like I am doing something wrong here. However, I am
    getting correct ctor's and dtor's, I am not sure how to access
    self from ct_foo() called from ct_thread()?


    std::thread::id id = std::this_thread::get_id();

    C tss uses a thread local array I think but I don't know
    it gets notification on individual thread exits so it
    can run the dtor.  C++ has a cvar notify all on thread
    exits.

    I have a different hack which I won't get around to
    until I need it.

    I still don't know how C++ calls dtors wrt per-thread objects via
    thread_local. My hack would be to use tss_create and be done with it.


    Ok, it looks like they are using libc atexit function (stdlib.h) to
    invoke tls cleanup on thread exit.

    Correction.  atexit is for process exit not thread exit.  So those
    start_ functions are where they are doing the exit handling.

    Well, here is a call stack for my most recent try wrt tss dtors in pure
    C++:

        ct_plot_pre_alpha.exe!ct_per_thread::~ct_per_thread() Line 39    C++
         ct_plot_pre_alpha.exe!`ct_thread'::`3'::`dynamic atexit destructor for 'self''()    C++
         ct_plot_pre_alpha.exe!__dyn_tls_dtor(void * __formal, const unsigned long dwReason, void * __formal) Line 122    C++
         ntdll.dll!00007ffbed20bfca()    Unknown
         ntdll.dll!00007ffbed1b8b5f()    Unknown
         ntdll.dll!00007ffbed1b97e9()    Unknown
         ntdll.dll!00007ffbed1b9428()    Unknown
         ntdll.dll!00007ffbed1eaf7e()    Unknown
         KernelBase.dll!00007ffbea68e1ca()    Unknown
         ucrtbased.dll!00007ffb83e932cb()    Unknown
         ucrtbased.dll!00007ffb83e93931()    Unknown
         ucrtbased.dll!00007ffb83e93017()    Unknown
         kernel32.dll!00007ffbec15257d()    Unknown
         ntdll.dll!00007ffbed1eaf08()    Unknown


    atexit is in there.


    Jow, well, there is __dyn_tls_dtor wrt MSVC.
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Chris M. Thomasson@chris.m.thomasson.1@gmail.com to comp.lang.c++ on Sun Oct 20 19:12:49 2024
    From Newsgroup: comp.lang.c++

    On 10/10/2024 12:20 PM, wij wrote:
    [...]

    Check this shit out:

    Here is my crude code:
    _______________________________________
    #include <iostream>
    #include <functional>
    #include <thread>
    #include <atomic>
    #include <mutex>


    #define CT_THREADS 5


    struct ct_shared
    {
    std::mutex m_cout_lock;
    };


    struct ct_per_thread
    {
    ct_shared& m_shared;
    unsigned long m_id;

    ct_per_thread(
    ct_shared& shared,
    unsigned long id

    ): m_shared(shared),
    m_id(id)
    {
    {
    std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
    std::cout << "ct_per_thread::ct_per_thread(" << m_id << ")"
    << std::endl;
    }
    }

    ~ct_per_thread()
    {
    {
    std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
    std::cout << "ct_per_thread::~ct_per_thread(" << m_id <<
    ")" << std::endl;
    }
    }
    };



    thread_local ct_per_thread* g_per_thread = nullptr;



    void
    ct_foo()
    {
    // Okay, what about this shit!
    {
    std::unique_lock<std::mutex> lock(g_per_thread->m_shared.m_cout_lock);
    std::cout << "ct_foo(" << g_per_thread->m_id << ")" << std::endl;
    }
    }


    void
    ct_thread(
    ct_shared& shared,
    unsigned long id
    ) {
    {
    thread_local ct_per_thread self(shared, id);

    g_per_thread = &self;
    }

    ct_foo();
    }


    int
    main()
    {
    std::cout << "ct_plot_pre_alpha... Testing 123! :^)\n\n";
    std::cout << "_____________________________________________" << std::endl;

    {
    ct_shared shared;

    {
    std::thread threads[CT_THREADS];

    std::cout << "launching " << CT_THREADS << " threads..." << std::endl;

    for (unsigned long i = 0; i < CT_THREADS; ++i)
    {
    threads[i] = std::thread(ct_thread, std::ref(shared), i);
    }

    for (unsigned long i = 0; i < CT_THREADS; ++i)
    {
    threads[i].join();
    }
    }
    }

    std::cout << "_____________________________________________\n";
    std::cout << "complete!\n";

    return 0;
    }
    _______________________________________


    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From wij@wyniijj5@gmail.com to comp.lang.c++ on Mon Oct 21 19:06:13 2024
    From Newsgroup: comp.lang.c++

    On Sun, 2024-10-20 at 19:12 -0700, Chris M. Thomasson wrote:
    On 10/10/2024 12:20 PM, wij wrote:
    [...]

    Check this shit out:

    Here is my crude code:
    _______________________________________
    #include <iostream>
    #include <functional>
    #include <thread>
    #include <atomic>
    #include <mutex>


    #define CT_THREADS 5


    struct ct_shared
    {
         std::mutex m_cout_lock;
    };


    struct ct_per_thread
    {
         ct_shared& m_shared;
         unsigned long m_id;

         ct_per_thread(
             ct_shared& shared,
             unsigned long id

         ):  m_shared(shared),
             m_id(id)
         {
             {
                 std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
                 std::cout << "ct_per_thread::ct_per_thread(" << m_id << ")"
    << std::endl;
             }
         }

         ~ct_per_thread()
         {
             {
                 std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
                 std::cout << "ct_per_thread::~ct_per_thread(" << m_id <<
    ")" << std::endl;
             }
         }
    };



    thread_local ct_per_thread* g_per_thread = nullptr;



    void
    ct_foo()
    {
         // Okay, what about this shit!
         {
             std::unique_lock<std::mutex> lock(g_per_thread->m_shared.m_cout_lock);
             std::cout << "ct_foo(" << g_per_thread->m_id << ")" << std::endl;
         }
    }


    void
    ct_thread(
         ct_shared& shared,
         unsigned long id
    ) {
         {
             thread_local ct_per_thread self(shared, id);

             g_per_thread = &self;
         }

         ct_foo();
    }


    int
    main()
    {
         std::cout << "ct_plot_pre_alpha... Testing 123! :^)\n\n";
         std::cout << "_____________________________________________" << std::endl;

         {
             ct_shared shared;

             {
                 std::thread threads[CT_THREADS];

                 std::cout << "launching " << CT_THREADS << " threads..." <<
    std::endl;

                 for (unsigned long i = 0; i < CT_THREADS; ++i)              {
                     threads[i] = std::thread(ct_thread, std::ref(shared), i);
                 }

                 for (unsigned long i = 0; i < CT_THREADS; ++i)              {
                     threads[i].join();
                 }
             }
         }

         std::cout << "_____________________________________________\n";      std::cout << "complete!\n";

         return 0;
    }
    _______________________________________
    Don't know what the program is demonstrating.
    The following is what I thought what ct_thread is doing.
    1. In libwy, each thread is represented by a PThread object, following the RAII
    principle. (your program looked to me std::thread API has no idea of RAII). 2. libwy only has Mutex (wrapper of pthread_mutex), because other kinds of
    mutexes are considered not often used and easily implemented (except futex)
    and many application variations, i.e. read/write mutex.
    3. Wy::PThread has no public facilities for 'thread specific' (thread local),
    because in most cases, 'thread local' can be implemented as class data
    member.
    (Wy::PThread is old from the time the underlying Linux Thread is buggy,
    and I am tired to revise it without real cases showing the necessarity.)
    // t.cpp
    #include <Wy.pthread.h>
    #include <Wy.stdio.h>

    using namespace Wy;
    class Thread : public PThread {
    Mutex *m_gmtx; // for 'thread common' data
    int m_id;
    // Many 'thread local' data can be put here.
    public:
    Thread(Mutex& mtx, int id) : m_gmtx(&mtx), m_id(id) {};
    Errno begin() override { return PThread::begin(); };
    ~Thread() {
    NoCancel noc;
    tmain_close(); // Stop possibly active thread, then destruction begins.
    cout << "~Thread(mtx," << m_id << ")" WY_ENDL;
    };
    protected:
    Errno tmain() override {
    Mutex::Lock aa(*m_gmtx);
    cout << "Thread(mtx," << m_id << ")" WY_ENDL;
    return Ok;
    };
    };
    #define CT_THREADS 5
    int main()
    try {
    Errno r;
    Mutex shared_mutex;
    Thread thrd[CT_THREADS]={
    Thread(shared_mutex,0),
    Thread(shared_mutex,1),
    Thread(shared_mutex,2),
    Thread(shared_mutex,3),
    Thread(shared_mutex,4),
    };
    for(int i=0; i<CT_THREADS; ++i) {
    if((r=thrd[i].begin())!=Ok) {
    WY_THROW(r);
    }
    }
    for(int i=0; i<CT_THREADS; ++i) {
    if((r=thrd[i].wait_if(PThread::Running))!=Ok) {
    WY_THROW(r);
    }
    }
    return 0;
    }
    catch(const Errno& e) {
    cerr << wrd(e) << WY_ENDL;
    throw;
    }
    catch(...) {
    cerr << "Unknown thrown object" WY_ENDL;
    throw;
    };
    -----------------------------
    []$ g++ t.cpp -lwy
    []$ ./a.out
    Thread(mtx,0)
    Thread(mtx,1)
    Thread(mtx,4)
    Thread(mtx,2)
    Thread(mtx,3)
    ~Thread(mtx,4)
    ~Thread(mtx,3)
    ~Thread(mtx,2)
    ~Thread(mtx,1)
    ~Thread(mtx,0)
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From wij@wyniijj5@gmail.com to comp.lang.c++ on Mon Oct 21 19:20:55 2024
    From Newsgroup: comp.lang.c++

    On Mon, 2024-10-21 at 19:06 +0800, wij wrote:
    On Sun, 2024-10-20 at 19:12 -0700, Chris M. Thomasson wrote:
    On 10/10/2024 12:20 PM, wij wrote:
    [...]

    Check this shit out:

    Here is my crude code:
    _______________________________________
    #include <iostream>
    #include <functional>
    #include <thread>
    #include <atomic>
    #include <mutex>


    #define CT_THREADS 5


    struct ct_shared
    {
         std::mutex m_cout_lock;
    };


    struct ct_per_thread
    {
         ct_shared& m_shared;
         unsigned long m_id;

         ct_per_thread(
             ct_shared& shared,
             unsigned long id

         ):  m_shared(shared),
             m_id(id)
         {
             {
                 std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
                 std::cout << "ct_per_thread::ct_per_thread(" << m_id << ")"
    << std::endl;
             }
         }

         ~ct_per_thread()
         {
             {
                 std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
                 std::cout << "ct_per_thread::~ct_per_thread(" << m_id <<
    ")" << std::endl;
             }
         }
    };



    thread_local ct_per_thread* g_per_thread = nullptr;



    void
    ct_foo()
    {
         // Okay, what about this shit!
         {
             std::unique_lock<std::mutex> lock(g_per_thread->m_shared.m_cout_lock);
             std::cout << "ct_foo(" << g_per_thread->m_id << ")" << std::endl;
         }
    }


    void
    ct_thread(
         ct_shared& shared,
         unsigned long id
    ) {
         {
             thread_local ct_per_thread self(shared, id);

             g_per_thread = &self;
         }

         ct_foo();
    }


    int
    main()
    {
         std::cout << "ct_plot_pre_alpha... Testing 123! :^)\n\n";
         std::cout << "_____________________________________________" << std::endl;

         {
             ct_shared shared;

             {
                 std::thread threads[CT_THREADS];

                 std::cout << "launching " << CT_THREADS << " threads..." <<
    std::endl;

                 for (unsigned long i = 0; i < CT_THREADS; ++i)              {
                     threads[i] = std::thread(ct_thread, std::ref(shared), i);
                 }

                 for (unsigned long i = 0; i < CT_THREADS; ++i)              {
                     threads[i].join();              }
             }
         }

         std::cout << "_____________________________________________\n";      std::cout << "complete!\n";

         return 0;
    }
    _______________________________________

    Don't know what the program is demonstrating.
    The following is what I thought what ct_thread is doing.

    1. In libwy, each thread is represented by a PThread object, following the RAII
       principle. (your program looked to me std::thread API has no idea of RAII).
    2. libwy only has Mutex (wrapper of pthread_mutex), because other kinds of    mutexes are considered not often used and easily implemented (except futex)
       and many application variations, i.e. read/write mutex.
    3. Wy::PThread has no public facilities for 'thread specific' (thread local),
       because in most cases, 'thread local' can be implemented as class data    member.
       (Wy::PThread is old from the time the underlying Linux Thread is buggy,     and I am tired to revise it without real cases showing the necessarity.)

    // t.cpp
    #include <Wy.pthread.h>
    #include <Wy.stdio.h>
       
    using namespace Wy;

    class Thread : public PThread {
        Mutex *m_gmtx;  // for 'thread common' data
        int   m_id;

        // Many 'thread local' data can be put here.
      public:
        Thread(Mutex& mtx, int id) : m_gmtx(&mtx), m_id(id) {};
        Errno begin() override { return PThread::begin(); };
        ~Thread() {
           NoCancel noc;
           tmain_close(); // Stop possibly active thread, then destruction begins.
           cout << "~Thread(mtx," << m_id << ")" WY_ENDL;
        };

      protected:
        Errno tmain() override {
          Mutex::Lock aa(*m_gmtx);
          cout << "Thread(mtx," << m_id << ")" WY_ENDL;
          return Ok;
        };
    };

    #define CT_THREADS 5

    int main()
    try {       
      Errno r;
      Mutex shared_mutex;
      Thread thrd[CT_THREADS]={
                       Thread(shared_mutex,0),                    Thread(shared_mutex,1),                    Thread(shared_mutex,2),                    Thread(shared_mutex,3),                    Thread(shared_mutex,4),                  };

     for(int i=0; i<CT_THREADS; ++i) {
       if((r=thrd[i].begin())!=Ok) {
         WY_THROW(r);
       }
     }

     for(int i=0; i<CT_THREADS; ++i) {
       if((r=thrd[i].wait_if(PThread::Running))!=Ok) {
         WY_THROW(r);
       }
     }

     return 0;
    }
    catch(const Errno& e) {
      cerr << wrd(e) << WY_ENDL;
      throw;
    }
    catch(...) {
      cerr << "Unknown thrown object" WY_ENDL;
      throw;
    };

    -----------------------------
    []$ g++ t.cpp -lwy
    []$ ./a.out
    Thread(mtx,0)
    Thread(mtx,1)
    Thread(mtx,4)
    Thread(mtx,2)
    Thread(mtx,3)
    ~Thread(mtx,4)
    ~Thread(mtx,3)
    ~Thread(mtx,2)
    ~Thread(mtx,1)
    ~Thread(mtx,0)




    Correction of the critic "your program looked to me std::thread API has no idea of RAII"
    from "threads[i] = std::thread(ct_thread, std::ref(shared), i);"
    It may just difference of implementation.

    --- Synchronet 3.20a-Linux NewsLink 1.114