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.
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
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]).