Discussion:
C++ equivalent of C tss_create
(too old to reply)
jseigh
2024-10-07 16:03:42 UTC
Permalink
tss_create lets you dynamically create thread local storage.
thread_local is static. Gets resolved at ld time. Doesn't
work too well if you want a per object instance of thread
local storage. Something like that in C++?

Joe Seigh
Chris M. Thomasson
2024-10-07 19:50:07 UTC
Permalink
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static.  Gets resolved at ld time.  Doesn't
work too well if you want a per object instance of thread
local storage.  Something like that in C++?
I don't think so. Fwiw, a while back I was trying to port one of my
thread local memory allocators that used pthread tss to pure C++ and
gave up. It has important logic in its destructor. The function pointer
in: pthread_key_create ala tss_create. So, shit happens! ;^o
Chris M. Thomasson
2024-10-07 19:54:02 UTC
Permalink
Post by Chris M. Thomasson
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static.  Gets resolved at ld time.  Doesn't
work too well if you want a per object instance of thread
local storage.  Something like that in C++?
I don't think so. Fwiw, a while back I was trying to port one of my
thread local memory allocators that used pthread tss to pure C++ and
gave up. It has important logic in its destructor. The function pointer
in: pthread_key_create ala tss_create. So, shit happens! ;^o
Perhaps I should revisit it to clear my mind. Humm...
jseigh
2024-10-07 22:16:58 UTC
Permalink
Post by Chris M. Thomasson
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static.  Gets resolved at ld time.  Doesn't
work too well if you want a per object instance of thread
local storage.  Something like that in C++?
I don't think so. Fwiw, a while back I was trying to port one of my
thread local memory allocators that used pthread tss to pure C++ and
gave up. It has important logic in its destructor. The function pointer
in: pthread_key_create ala tss_create. So, shit happens! ;^o
I suppose you could use a thread local map with logic on top of that.

I probably don't need it. I was using it in C to get notification
when a thread exited for clean up of any resources the thread had
not explicitly cleaned up. I could come up with a thread exit
listener if need be.

Joe Seigh
Chris M. Thomasson
2024-10-08 00:46:06 UTC
Permalink
Post by jseigh
Post by Chris M. Thomasson
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static.  Gets resolved at ld time.  Doesn't
work too well if you want a per object instance of thread
local storage.  Something like that in C++?
I don't think so. Fwiw, a while back I was trying to port one of my
thread local memory allocators that used pthread tss to pure C++ and
gave up. It has important logic in its destructor. The function
pointer in: pthread_key_create ala tss_create. So, shit happens! ;^o
I suppose you could use a thread local map with logic on top of that.
That would be like recreating a PThread impl? C with tss_create is okay.
I remember way back trying to get a dtor per thread by using an object
with thread_local. I need to revisit it. Perhaps compilers got much
better. They should have! Or else, the C API is fine as it more closely
goes with PThreads?
Post by jseigh
I probably don't need it.  I was using it in C to get notification
when a thread exited for clean up of any resources the thread had
not explicitly cleaned up.  I could come up with a thread exit
listener if need be.
Joe Seigh
Chris M. Thomasson
2024-10-08 00:47:29 UTC
Permalink
Post by jseigh
Post by Chris M. Thomasson
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static.  Gets resolved at ld time.  Doesn't
work too well if you want a per object instance of thread
local storage.  Something like that in C++?
I don't think so. Fwiw, a while back I was trying to port one of my
thread local memory allocators that used pthread tss to pure C++ and
gave up. It has important logic in its destructor. The function
pointer in: pthread_key_create ala tss_create. So, shit happens! ;^o
I suppose you could use a thread local map with logic on top of that.
I probably don't need it.  I was using it in C to get notification
when a thread exited for clean up of any resources the thread had
not explicitly cleaned up.
Ditto!
Post by jseigh
I could come up with a thread exit
listener if need be.
Bonita Montero
2024-10-08 04:53:41 UTC
Permalink
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static.  Gets resolved at ld time.  Doesn't
work too well if you want a per object instance of thread
local storage.  Something like that in C++?
Joe Seigh
A thread_local object declared globally is constructed when a new
thread starts and destructed when a thread ends. A thread_local
object declared locally is constructed when the code comes across
its declaration and destructed when the thread ends.
Chris M. Thomasson
2024-10-08 06:29:41 UTC
Permalink
Post by Bonita Montero
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static.  Gets resolved at ld time.  Doesn't
work too well if you want a per object instance of thread
local storage.  Something like that in C++?
Joe Seigh
A thread_local object declared globally is constructed when a new
thread starts and destructed when a thread ends. A thread_local
object declared locally is constructed when the code comes across
its declaration and destructed when the thread ends.
Well, that should do it. Last time I checked, which was a while back,
well, I was having trouble getting dtors to be called.

So, psuedo code here:

void ct_thread()
{
thread_local per_thread_data();
}

If I create a single thread, then I should get one ctor and one dtor
with the dtor being called within the thread, right?

If I create three threads, three ctors and three dtors and per their
threads context, right?
Bonita Montero
2024-10-08 07:02:35 UTC
Permalink
Post by Chris M. Thomasson
void ct_thread()
{
    thread_local per_thread_data();
}
If I create a single thread, then I should get one ctor and
one dtor with the dtor being called within the thread, right?
per_thread_data is created only if the thread comes across the
definition. If you need reliable behaviour according to each
created thread make per_thread_data global.
Chris M. Thomasson
2024-10-08 19:00:19 UTC
Permalink
Post by Bonita Montero
Post by Chris M. Thomasson
void ct_thread()
{
     thread_local per_thread_data();
}
If I create a single thread, then I should get one ctor and
one dtor with the dtor being called within the thread, right?
per_thread_data is created only if the thread comes across the
definition. If you need reliable behaviour according to each
created thread make per_thread_data global.
Sounds right to me. The last time I tried it (C++11 years ago), some of
the dtors were not being called. The ctors were called... So, I just
need to get back into it...

Thanks Bonita. :^)
Sam
2024-10-08 12:26:56 UTC
Permalink
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static. Gets resolved at ld time. Doesn't
work too well if you want a per object instance of thread
local storage. Something like that in C++?
Not in C++ proper, but gcc supports it:

https://gcc.gnu.org/onlinedocs/gcc/Thread-Local.html
Bonita Montero
2024-10-08 14:32:33 UTC
Permalink
Post by Sam
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static.  Gets resolved at ld time.  Doesn't
work too well if you want a per object instance of thread
local storage.  Something like that in C++?
https://gcc.gnu.org/onlinedocs/gcc/Thread-Local.html
This doesn't help you if you want to get notified when a thread-local
object is being destructed.
Chris M. Thomasson
2024-10-08 19:02:12 UTC
Permalink
Post by Bonita Montero
Post by Sam
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static.  Gets resolved at ld time.  Doesn't
work too well if you want a per object instance of thread
local storage.  Something like that in C++?
https://gcc.gnu.org/onlinedocs/gcc/Thread-Local.html
This doesn't help you if you want to get notified when a thread-local
object is being destructed.
That is very important for a lot of my older work.
jseigh
2024-10-08 21:03:23 UTC
Permalink
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static.  Gets resolved at ld time.  Doesn't
work too well if you want a per object instance of thread
local storage.  Something like that in C++?
Joe Seigh
I figured something out so all good I think.
Chris M. Thomasson
2024-10-08 21:16:17 UTC
Permalink
Post by jseigh
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static.  Gets resolved at ld time.  Doesn't
work too well if you want a per object instance of thread
local storage.  Something like that in C++?
[...]
Post by jseigh
I figured something out so all good I think.
Iirc, a while back I was using placement new to create an object in tss.
Then in the tss dtor function I would explicitly call the dtor of said
object. Shit happens. ;^)
jseigh
2024-10-13 17:56:22 UTC
Permalink
Post by jseigh
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static.  Gets resolved at ld time.  Doesn't
work too well if you want a per object instance of thread
local storage.  Something like that in C++?
Joe Seigh
I figured something out so all good I think.
So basically just declare a thread local array. It will have
fixed size and you will have to manage allocation of array
slots and dtors and stuff. E.g.

thread_local void * x2[20];

Accessing a slot value, if you inline it with a uint64_t
key on x86 will get you something like

movq %fs:***@tpoff(,%rax,8), %rdx

with %rax containing the index, i.e. the key,

which will beat calling tss_get which calls
pthread_getspecific.

Joe Seigh
Chris M. Thomasson
2024-10-14 23:09:14 UTC
Permalink
Post by jseigh
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static.  Gets resolved at ld time.  Doesn't
work too well if you want a per object instance of thread
local storage.  Something like that in C++?
Joe Seigh
I figured something out so all good I think.
So basically just declare a thread local array.  It will have
fixed size and you will have to manage allocation of array
slots and dtors and stuff.  E.g.
thread_local void * x2[20];
Accessing a slot value, if you inline it with a uint64_t
key on x86 will get you something like
with %rax containing the index, i.e. the key,
which will beat calling tss_get which calls
pthread_getspecific.
Interesting. I need to check the call stack of accessing a thread_local
pointer. The g_per_thread wrt my recent code's ct_foo function that is
called from ct_thread:

void
ct_foo()
{
// Okay, what about this shit!
{
std::unique_lock<std::mutex>
lock(g_per_thread->m_shared.m_cout_lock);
std::cout << "ct_foo(" << g_per_thread->m_id << ")" << std::endl;
}
}


Full code for reference:
_______________________________________
#include <iostream>
#include <functional>
#include <thread>
#include <atomic>
#include <mutex>


#define CT_THREADS 5


struct ct_shared
{
std::mutex m_cout_lock;
};


struct ct_per_thread
{
ct_shared& m_shared;
unsigned long m_id;

ct_per_thread(
ct_shared& shared,
unsigned long id

): m_shared(shared),
m_id(id)
{
{
std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
std::cout << "ct_per_thread::ct_per_thread(" << m_id << ")"
<< std::endl;
}
}

~ct_per_thread()
{
{
std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
std::cout << "ct_per_thread::~ct_per_thread(" << m_id <<
")" << std::endl;
}
}
};



thread_local ct_per_thread* g_per_thread = nullptr;



void
ct_foo()
{
// Okay, what about this shit!
{
std::unique_lock<std::mutex>
lock(g_per_thread->m_shared.m_cout_lock);
std::cout << "ct_foo(" << g_per_thread->m_id << ")" << std::endl;
}
}


void
ct_thread(
ct_shared& shared,
unsigned long id
) {
{
thread_local ct_per_thread self(shared, id);

g_per_thread = &self;
}

ct_foo();
}


int
main()
{
std::cout << "ct_plot_pre_alpha... Testing 123! :^)\n\n";
std::cout << "_____________________________________________" <<
std::endl;

{
ct_shared shared;

{
std::thread threads[CT_THREADS];

std::cout << "launching " << CT_THREADS << " threads..." <<
std::endl;

for (unsigned long i = 0; i < CT_THREADS; ++i)
{
threads[i] = std::thread(ct_thread, std::ref(shared), i);
}

for (unsigned long i = 0; i < CT_THREADS; ++i)
{
threads[i].join();
}
}
}

std::cout << "_____________________________________________\n";
std::cout << "complete!\n";

return 0;
}
_______________________________________

Chris M. Thomasson
2024-10-10 18:55:11 UTC
Permalink
Post by jseigh
tss_create lets you dynamically create thread local storage.
thread_local is static.  Gets resolved at ld time.  Doesn't
work too well if you want a per object instance of thread
local storage.  Something like that in C++?
Something like this seems "okay" to me unless I am missing something.
Humm... I still prefer the tss_create function.

_________________________
#include <iostream>
#include <functional>
#include <thread>
#include <atomic>
#include <mutex>


#define CT_THREADS 5


struct ct_shared
{
std::mutex m_cout_lock;
};


struct ct_per_thread
{
ct_shared& m_shared;
unsigned long m_id;

ct_per_thread(
ct_shared& shared,
unsigned long id

): m_shared(shared),
m_id(id)
{
{
std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
std::cout << "ct_per_thread::ct_per_thread(" << m_id << ")"
<< std::endl;
}
}

~ct_per_thread()
{
{
std::unique_lock<std::mutex> lock(m_shared.m_cout_lock);
std::cout << "ct_per_thread::~ct_per_thread(" << m_id <<
")" << std::endl;
}
}
};



thread_local ct_per_thread* g_per_thread = nullptr;



void
ct_foo()
{
// Okay, what about this shit!
{
std::unique_lock<std::mutex>
lock(g_per_thread->m_shared.m_cout_lock);
std::cout << "ct_foo(" << g_per_thread->m_id << ")" << std::endl;
}
}


void
ct_thread(
ct_shared& shared,
unsigned long id
) {
ct_per_thread self(shared, id);

g_per_thread = &self;

ct_foo();
}


int
main()
{
std::cout << "ct_plot_pre_alpha... Testing 123! :^)\n\n";
std::cout << "_____________________________________________" <<
std::endl;

{
ct_shared shared;

{
std::thread threads[CT_THREADS];

std::cout << "launching " << CT_THREADS << " threads..." <<
std::endl;

for (unsigned long i = 0; i < CT_THREADS; ++i)
{
threads[i] = std::thread(ct_thread, std::ref(shared), i);
}

for (unsigned long i = 0; i < CT_THREADS; ++i)
{
threads[i].join();
}
}
}

std::cout << "_____________________________________________\n";
std::cout << "complete!\n";

return 0;
}
_________________________
Loading...