【问题标题】:c++ Schwarz counter with thread_localc ++ Schwarz计数器与thread_local
【发布时间】:2017-11-09 12:45:27
【问题描述】:

我可以将Schwarz counter (aka 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;

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

更新

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

【问题讨论】:

  • 您还需要对 ThisThread 的引用也是 thread_local。
  • @RichardHodges 你是什么意思?
  • 秒,将敲出一个演示
  • 这是一个有效的包装器:w01fe.com/blog/2009/05/…
  • @AlexCohn 与问题相同。并且有同样的问题——它的析构函数可能在其他 thread_local 和静态析构函数之前被调用。我想我几乎有这个通用的解决方案,我会用这个发布答案。 Richard Hodges 单独使用 thread_local 漂亮的计数器是不够的,因为静态析构函数也可能尝试访问 env(并且 thread_local 在静态 + 销毁之前被破坏可能不在主线程 stackoverflow.com/a/47208350/1559666 上发生)。

标签: c++ java-native-interface static-order-fiasco


【解决方案1】:

我认为最好的解决方案是正常实现 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

【讨论】:

  • @tower120 为什么你认为它不起作用?我看到构造函数和析构函数按预期调用。
  • 抱歉,这似乎是由于 mingw-w64 错误,或我的工具链 stackoverflow.com/questions/47226542/… 中的其他一些故障。我尝试使用 VS 2017,它似乎在该测试中按预期工作。但是......我仍然感到困惑 - 我们实际上并没有计算 thread_locals......
【解决方案2】:

这没有回答如何为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可能不会在第一轮最后被调用])。

【讨论】:

    猜你喜欢
    • 2019-11-20
    • 2018-03-11
    • 2020-01-23
    • 1970-01-01
    • 2019-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-06
    相关资源
    最近更新 更多