带有thread_local的 c++ 施瓦茨计数器

c++ Schwarz counter with thread_local

本文关键字:c++ 施瓦茨 计数器 local thread 带有      更新时间:2023-10-16

我可以将施瓦茨计数器(又名Nifty计数器)成语与thread_local一起使用吗?(假设我用thread_local替换所有static

我需要这个(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;

首先构造,最后在每个线程中销毁。我可以从其他静态或thread_local对象的析构函数/构造函数调用this_thread->getEnv()

更新

https://stackoverflow.com/a/30200992 - 在这里,标准说thread_local称为 BEFORE 静态的析构函数,我需要这个在之后。

我认为最好的解决方案是正常实现 schwartz 计数器,但以thread_local静态Impl的方式实现 ThisThread 类。

带有输出的完整示例:

// 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将在C++的静态thread_locals(或交错)之前被调用[换句话说ThisThread在最后一次使用之前被销毁],在下次调用对象(getEnv())时,我们有点重新初始化/重新创建它并注册pthread_dstr进行另一轮。

:注:我们总共最多可以有 PTHREAD_DESTRUCTOR_ITERATIONS 轮,即 4 轮。但是在最坏的情况下,我们总是会得到第二个(如果C++ thread_local实现将使用p_thread析构函数[这意味着我们的pthread_dstr可能不会在第一轮中被最后调用])。