C++ счётчик Шварца с thread_local

Могу ли я использовать счетчик Schwarz (он же Nifty counter), с thread_local? (Предполагая, что я заменяю все static с thread_local)

Мне нужно это (помощник для Java JNI потоков):

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;

Должен быть построен первым и уничтожен последним в каждом потоке. Я могу позвонить this_thread->getEnv() от деструктора / конструктора другого статического объекта или объекта thread_local.

ОБНОВИТЬ

/questions/6299374/c-static-vs-poryadok-unichtozheniya-prodolzhitelnosti-hraneniya-potoka/6299382#6299382 - здесь стандарт говорит, что деструкторы thread_local называются ДО статического, и мне нужно, чтобы этот был после.

2 ответа

Я думаю, что лучшее решение - реализовать счетчик Шварца как обычно, но реализовать ThisThread класс с точки зрения thread_local статический Impl,

Полный пример с выводами:

// 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();
}

пример вывода:

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

Это не ответ, как сделать счетчик Шварца для thread_local static (так что я не принимаю это как ответ). Но, в конце концов, я придумал это решение, зависящее от платформы (Linux/Android).

#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;
}

Даже если по какой-то причине pthread_dstr будет вызываться перед статическим thread_locals (или с чередованием) в C++ [другими словами ThisThread уничтожено до последнего использования], при следующем вызове объекта (getEnv()) мы вроде как заново инициализируем / воссоздаем его и регистрируем pthread_dstr для другого раунда.

NB В общем максимуме мы можем иметь PTHREAD_DESTRUCTOR_ITERATIONS раундов, что равно 4. Но мы всегда окажемся на втором, в худшем случае (если реализация C++ thread_local будет использовать деструкторы p_thread [что будет означать, что OUR pthread_dstr не может называться последним в первом раунде]).

Другие вопросы по тегам