c++ Schwarz counter with thread_local

2019-07-29 04:37发布

Can I use Schwarz counter (aka Nifty counter) idiom, with thread_local? (Assuming I replace all static with thread_local)

I need this (helper for java jni threads):

class ThisThread{
    JNIEnv* jni_env{nullptr};
public:
    JNIEnv* getEnv(){
        if (!jni_env){
            // Attach thread
            java_vm->GetEnv((void**)&jni_env, JNI_VERSION);
            java_vm->AttachCurrentThread(&jni_env, NULL);
        }

        return jni_env;
    }

    ~ThisThread(){
        if (!jni_env) return;
        // Deattach thread
        java_vm->DetachCurrentThread();
    }
};

static thread_local ThisThread this_thread;

To be constructed first, and destructed last in each thread. I may call this_thread->getEnv() from destructor/constructor of other static or thread_local object.

UPDATE

https://stackoverflow.com/a/30200992 - here, standard says that thread_local destructors called BEFORE static, and I need this one to be after.

2条回答
劳资没心,怎么记你
2楼-- · 2019-07-29 04:57

I think the best solution is to implement the schwartz counter as normal, but implement the ThisThread class in terms of a thread_local static Impl.

Complete example with outputs:

// header file
#include <memory>
#include <mutex>
#include <iostream>
#include <thread>

std::mutex emit_mutex;

template<class...Ts>
void emit(Ts&&...ts)
{
    auto action = [](auto&&x) { std::cout << x; };
    auto lock = std::unique_lock<std::mutex>(emit_mutex);

    using expand = int[];
    expand{ 0,
        (action(std::forward<Ts>(ts)), 0)...
    };
}


struct ThisThread
{
    struct Impl
    {
        Impl()
        {
            emit("ThisThread created on thread ", std::this_thread::get_id(), '\n');
        }
        ~Impl()
        {
            emit("ThisThread destroyed on thread ", std::this_thread::get_id(), '\n');
        }
        void foo() 
        { 
            emit("foo on thread ", std::this_thread::get_id(), '\n');
        }
    };

    decltype(auto) foo() { return get_impl().foo(); }

private:
    static Impl& get_impl() { return impl_; }
    static thread_local Impl impl_;
};

struct ThisThreadInit
{

    ThisThreadInit();
    ~ThisThreadInit();

    static int initialised;
};

extern ThisThread& thisThread;
static ThisThreadInit thisThreadInit;



// cppfile

static std::aligned_storage_t<sizeof(ThisThread), alignof(ThisThread)> storage;
ThisThread& thisThread = *reinterpret_cast<ThisThread*>(std::addressof(storage));
int ThisThreadInit::initialised;
thread_local ThisThread::Impl ThisThread::impl_;

ThisThreadInit::ThisThreadInit()
{
    if (0 == initialised++)
    {
        new (std::addressof(storage)) ThisThread ();    
    }
}

ThisThreadInit::~ThisThreadInit()
{
    if (0 == --initialised)
    {
        thisThread.~ThisThread();
    }
}


// now use the object

#include <thread>

int main()
{
    thisThread.foo();

    auto t = std::thread([]{ thisThread.foo(); });
    t.join();
}

example output:

ThisThread created on thread 140475785611072
foo on thread 140475785611072
ThisThread created on thread 140475768067840
foo on thread 140475768067840
ThisThread destroyed on thread 140475768067840
ThisThread destroyed on thread 140475785611072
查看更多
仙女界的扛把子
3楼-- · 2019-07-29 04:57

This does not answer how to make Schwarz counter for thread_local static 's (so I don't accept this as answer). But in the end, I came up with this platform-dependent(Linux/Android) solution.

#include <jni.h>
#include <cassert>
#include "JavaVM.h"

namespace jni_interface{

    class ThisThread{
        inline static thread_local pthread_key_t p_key;

        static void pthread_dstr(void *arg){
            if (!jni_env) return;
            java_vm->DetachCurrentThread();
            jni_env = nullptr;

            pthread_setspecific(p_key, NULL);
            pthread_key_delete(p_key);
        }

        static void register_dstr(void *arg){
            {
                const int res = pthread_key_create(&p_key, pthread_dstr);
                assert(res != EAGAIN);
                assert(res != ENOMEM);
                assert(res == 0);
            }
            {
                const int res = pthread_setspecific(p_key, arg);
                assert(res == 0);
            }
        }

        inline static thread_local JNIEnv* jni_env{nullptr};
    public:
        JNIEnv* getEnv(){
            if (!jni_env){
                assert(java_vm);
                java_vm->GetEnv((void**)&jni_env, JNI_VERSION);
                java_vm->AttachCurrentThread(&jni_env, NULL);       // safe to call in main thread

                register_dstr(jni_env);
            }

            return jni_env;
        }
    };

    static thread_local ThisThread this_thread;
}

Even if by some reason, pthread_dstr will be called before C++'s static thread_locals (or interleaved) [in other words ThisThread destroyed before last use], on next call to object (getEnv()) we kinda re-init/re-create it and register pthread_dstr for another round.

N.B. At total maximum we can have PTHREAD_DESTRUCTOR_ITERATIONS rounds, which is 4. But we always will end up on a second one, at worst case (if C++ thread_local implementation will use p_thread destructors [which will mean that OUR pthread_dstr may not be called last in the first round]).

查看更多
登录 后发表回答