Могу ли я использовать Счетчик Шварца (иначе Счетчик Nifty) идиома, с 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.
ОБНОВИТЬ
https://stackoverflow.com/a/30200992 — здесь стандарт говорит, что деструкторы thread_local называются ДО статическими, и мне нужен этот после.
Я думаю, что лучшее решение — реализовать счетчик Шварца как обычно, но реализовать 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
«s (поэтому я не принимаю это как ответ). Но, в конце концов, я придумал это решение, зависящее от платформы (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
для другого раунда.
Нотабене В целом максимум мы можем иметь PTHREAD_DESTRUCTOR_ITERATIONS
раундов, что равно 4. Но мы всегда окажемся на втором, в худшем случае (если реализация C ++ thread_local будет использовать деструкторы p_thread [что будет означать, что OUR pthread_dstr
не может называться последним в первом раунде]).