Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.c++ > #120670 > unrolled thread
| Started by | "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> |
|---|---|
| First post | 2024-10-09 19:02 -0700 |
| Last post | 2024-10-12 13:43 -0700 |
| Articles | 20 on this page of 23 — 4 participants |
Back to article view | Back to comp.lang.c++
thread_local question... "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2024-10-09 19:02 -0700
Re: thread_local question... "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2024-10-09 19:08 -0700
Re: thread_local question... Andrey Tarasevich <andreytarasevich@hotmail.com> - 2024-10-09 19:33 -0700
Re: thread_local question... Andrey Tarasevich <andreytarasevich@hotmail.com> - 2024-10-09 19:40 -0700
Re: thread_local question... "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2024-10-09 20:08 -0700
Re: thread_local question... "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2024-10-09 20:19 -0700
Re: thread_local question... jseigh <jseigh_es00@xemaps.com> - 2024-10-10 07:52 -0400
Re: thread_local question... "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2024-10-10 12:04 -0700
Re: thread_local question... wij <wyniijj5@gmail.com> - 2024-10-11 03:20 +0800
Re: thread_local question... "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2024-10-20 19:12 -0700
Re: thread_local question... wij <wyniijj5@gmail.com> - 2024-10-21 19:06 +0800
Re: thread_local question... wij <wyniijj5@gmail.com> - 2024-10-21 19:20 +0800
Re: thread_local question... jseigh <jseigh_es00@xemaps.com> - 2024-10-10 15:57 -0400
Re: thread_local question... "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2024-10-11 13:36 -0700
Re: thread_local question... jseigh <jseigh_es00@xemaps.com> - 2024-10-11 09:50 -0400
Re: thread_local question... "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2024-10-11 12:21 -0700
Re: thread_local question... "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2024-10-11 22:53 -0700
Re: thread_local question... "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2024-10-11 22:56 -0700
Re: thread_local question... jseigh <jseigh_es00@xemaps.com> - 2024-10-11 17:05 -0400
Re: thread_local question... "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2024-10-12 13:44 -0700
Re: thread_local question... "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2024-10-12 13:46 -0700
Re: thread_local question... "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2024-10-12 13:29 -0700
Re: thread_local question... "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> - 2024-10-12 13:43 -0700
Page 1 of 2 [1] 2 Next page →
| From | "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> |
|---|---|
| Date | 2024-10-09 19:02 -0700 |
| Subject | thread_local question... |
| Message-ID | <ve7cj8$2vun0$1@dont-email.me> |
How can I get access to self in my ct_foo() function below? Am I
misusing thread_local here? I want self to be able to be accessible from
any function that my ct_thread() function calls. It feels like I am
doing something wrong here. However, I am getting correct ctor's and
dtor's, I am not sure how to access self from ct_foo() called from
ct_thread()?
I must be missing something obvious.
First of all can you compile and run it?
_______________________________
#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;
}
}
};
void
ct_foo()
{
// Ummm, how to get at self?
}
void
ct_thread(
ct_shared& shared,
unsigned long id
) {
// Well, self should be per thread...
thread_local ct_per_thread self(shared, id);
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;
}
_______________________________
Here is one of the outputs I get:
____________
ct_plot_pre_alpha... Testing 123! :^)
_____________________________________________
launching 5 threads...
ct_per_thread::ct_per_thread(0)
ct_per_thread::ct_per_thread(2)
ct_per_thread::ct_per_thread(3)
ct_per_thread::ct_per_thread(1)
ct_per_thread::ct_per_thread(4)
ct_per_thread::~ct_per_thread(0)
ct_per_thread::~ct_per_thread(2)
ct_per_thread::~ct_per_thread(3)
ct_per_thread::~ct_per_thread(1)
ct_per_thread::~ct_per_thread(4)
_____________________________________________
complete!
____________
Still, I think the tss_create() is better. Perhaps because it's more in
line with PThreads... ;^)
Any thoughts?
[toc] | [next] | [standalone]
| From | "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> |
|---|---|
| Date | 2024-10-09 19:08 -0700 |
| Message-ID | <ve7cut$2vun0$2@dont-email.me> |
| In reply to | #120670 |
On 10/9/2024 7:02 PM, Chris M. Thomasson wrote: > How can I get access to self in my ct_foo() function below? Am I > misusing thread_local here? I want self to be able to be accessible from > any function that my ct_thread() function calls. It feels like I am > doing something wrong here. However, I am getting correct ctor's and > dtor's, I am not sure how to access self from ct_foo() called from > ct_thread()? > > I must be missing something obvious. > > First of all can you compile and run it? > _______________________________ [...] > Still, I think the tss_create() is better. Perhaps because it's more in > line with PThreads... ;^) > > Any thoughts? I think I could store a per-thread pointer to it in a global via thread_local. Humm...
[toc] | [prev] | [next] | [standalone]
| From | Andrey Tarasevich <andreytarasevich@hotmail.com> |
|---|---|
| Date | 2024-10-09 19:33 -0700 |
| Message-ID | <ve7eek$30408$1@dont-email.me> |
| In reply to | #120670 |
On 10/09/24 7:02 PM, Chris M. Thomasson wrote:
>
> void
> ct_thread(
> ct_shared& shared,
> unsigned long id
> ) {
> // Well, self should be per thread...
> thread_local ct_per_thread self(shared, id);
>
> ct_foo();
> }
>
Um... Declaring a `thread_local` variable in block scope can serve only
one purpose: to _hide_ the variable, to prevent anyone else from
accessing it directly. `thread_local` is somewhat similar to `static` in
single-threaded applications: the variable retains its value even when
you exit and re-enter the scope. In all other respects the variable
itself remain invisible to the outside scopes, which is the whole
purpose of declaring it in block scope.
This is exactly what you did: you declared a variable as `thread_local`
in block scope. Which means it will retain its value when you exit and
re-enter the scope, but (as opposed to `static`) it is going to be a
per-thread variable. Since you declared it locally, it implies that you
_wanted_ to make it inaccessible from other scopes. Why are asking "how
to get at self?" then?
The only way to access such variable from other scopes is to access it
indirectly: through a reference/pointer that you create in advance
(where the variable is visible) and then somehow hand that
reference/pointer over to other scopes. If you don't what to fiddle with
indirect access, then declare your variable as `thread_local` in
namespace scope. That way you will easily be able to get at it from
other functions.
However, the real question here is: why you are even declaring your
variable as `thread_local`? A regular local variable inside a thread
function is already as "thread local" as it can possibly get. The only
thing that `thread_local` will give you is the retention of the old
value between calls. Do you really need that?
--
Best regards,
Andrey
[toc] | [prev] | [next] | [standalone]
| From | Andrey Tarasevich <andreytarasevich@hotmail.com> |
|---|---|
| Date | 2024-10-09 19:40 -0700 |
| Message-ID | <ve7ero$308e7$1@dont-email.me> |
| In reply to | #120672 |
On 10/09/24 7:33 PM, Andrey Tarasevich wrote:
> On 10/09/24 7:02 PM, Chris M. Thomasson wrote:
>>
>> void
>> ct_thread(
>> ct_shared& shared,
>> unsigned long id
>> ) {
>> // Well, self should be per thread...
>> thread_local ct_per_thread self(shared, id);
>>
>> ct_foo();
>> }
>>
>
>
> However, the real question here is: why you are even declaring your
> variable as `thread_local`? A regular local variable inside a thread
> function is already as "thread local" as it can possibly get. The only
> thing that `thread_local` will give you is the retention of the old
> value between calls. Do you really need that?
>
An additional remark:
The "retention of the old value between calls" would apply if you
declared a variable like that in some nested function invoked multiple
times from the top-level thread function.
But in your example the variable is declared in the top-level thread
function itself. In that case there will be no "retention of the old
value between calls" simply because once the thread function ends, the
thread ends as well, and all its `thread_local` variables die anyway.
So, your declaration does not seem to make any practical sense.
--
Best regards,
Andrey
[toc] | [prev] | [next] | [standalone]
| From | "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> |
|---|---|
| Date | 2024-10-09 20:08 -0700 |
| Message-ID | <ve7gg8$30d1s$1@dont-email.me> |
| In reply to | #120673 |
On 10/9/2024 7:40 PM, Andrey Tarasevich wrote:
> On 10/09/24 7:33 PM, Andrey Tarasevich wrote:
>> On 10/09/24 7:02 PM, Chris M. Thomasson wrote:
>>>
>>> void
>>> ct_thread(
>>> ct_shared& shared,
>>> unsigned long id
>>> ) {
>>> // Well, self should be per thread...
>>> thread_local ct_per_thread self(shared, id);
>>>
>>> ct_foo();
>>> }
>>>
>>
>>
>> However, the real question here is: why you are even declaring your
>> variable as `thread_local`? A regular local variable inside a thread
>> function is already as "thread local" as it can possibly get. The only
>> thing that `thread_local` will give you is the retention of the old
>> value between calls. Do you really need that?
>>
>
> An additional remark:
>
> The "retention of the old value between calls" would apply if you
> declared a variable like that in some nested function invoked multiple
> times from the top-level thread function.
>
> But in your example the variable is declared in the top-level thread
> function itself. In that case there will be no "retention of the old
> value between calls" simply because once the thread function ends, the
> thread ends as well, and all its `thread_local` variables die anyway.
> So, your declaration does not seem to make any practical sense.
>
How can I make my thread_local ct_per_thread self(shared, id) local to
all threads? I am missing something obvious, I think... Shit. This is
rather easily handled with tss_create or pthread_get/setspecific. I am
missing something here, big time. Sorry!, shit.
Well, I am not sure how to pass per thread information into a
thread_local _object_ that is global wrt a per thread ctor. I am so used
to the pthread way of doing things where the pthread_key_t would be
global... Actually, the C way now. tss_create and friends. Or the
tss_key in:
https://en.cppreference.com/w/c/thread/tss_create
Perhaps a global of:
thread_local ct_per_thread* g_tss = nullptr; // ?
? Ahh shit. I just need to try it out. I would want the tss dtors to be
called on a per thread basis, way deep in thread shutdown...
[toc] | [prev] | [next] | [standalone]
| From | "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> |
|---|---|
| Date | 2024-10-09 20:19 -0700 |
| Message-ID | <ve7h3g$30h8k$1@dont-email.me> |
| In reply to | #120672 |
On 10/9/2024 7:33 PM, Andrey Tarasevich wrote:
> On 10/09/24 7:02 PM, Chris M. Thomasson wrote:
>>
>> void
>> ct_thread(
>> ct_shared& shared,
>> unsigned long id
>> ) {
>> // Well, self should be per thread...
>> thread_local ct_per_thread self(shared, id);
>>
>> ct_foo();
>> }
>>
>
> Um... Declaring a `thread_local` variable in block scope can serve only
> one purpose: to _hide_ the variable, to prevent anyone else from
> accessing it directly. `thread_local` is somewhat similar to `static` in
> single-threaded applications: the variable retains its value even when
> you exit and re-enter the scope. In all other respects the variable
> itself remain invisible to the outside scopes, which is the whole
> purpose of declaring it in block scope.[...]
Okay, well, is this any better, or worse!
;^o
_________________________
#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;
}
_________________________
[toc] | [prev] | [next] | [standalone]
| From | jseigh <jseigh_es00@xemaps.com> |
|---|---|
| Date | 2024-10-10 07:52 -0400 |
| Message-ID | <ve8f6l$34vt2$1@dont-email.me> |
| In reply to | #120670 |
On 10/9/24 22:02, Chris M. Thomasson wrote: > How can I get access to self in my ct_foo() function below? Am I > misusing thread_local here? I want self to be able to be accessible from > any function that my ct_thread() function calls. It feels like I am > doing something wrong here. However, I am getting correct ctor's and > dtor's, I am not sure how to access self from ct_foo() called from > ct_thread()? > std::thread::id id = std::this_thread::get_id(); C tss uses a thread local array I think but I don't know it gets notification on individual thread exits so it can run the dtor. C++ has a cvar notify all on thread exits. I have a different hack which I won't get around to until I need it. Joe Seigh
[toc] | [prev] | [next] | [standalone]
| From | "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> |
|---|---|
| Date | 2024-10-10 12:04 -0700 |
| Message-ID | <ve98fe$392tv$2@dont-email.me> |
| In reply to | #120683 |
On 10/10/2024 4:52 AM, jseigh wrote:
> On 10/9/24 22:02, Chris M. Thomasson wrote:
>> How can I get access to self in my ct_foo() function below? Am I
>> misusing thread_local here? I want self to be able to be accessible
>> from any function that my ct_thread() function calls. It feels like I
>> am doing something wrong here. However, I am getting correct ctor's
>> and dtor's, I am not sure how to access self from ct_foo() called from
>> ct_thread()?
>>
>
> std::thread::id id = std::this_thread::get_id();
>
> C tss uses a thread local array I think but I don't know
> it gets notification on individual thread exits so it
> can run the dtor. C++ has a cvar notify all on thread
> exits.
>
> I have a different hack which I won't get around to
> until I need it.
I still don't know how C++ calls dtors wrt per-thread objects via
thread_local. My hack would be to use tss_create and be done with it.
However, this "works", but the only reason the dtor is being called has
nothing to do with thread_local. I resorted to using thread_local for a
pointer to a local per_thread object on the threads stack. It's dtor
means the thread exited. However, this is a lot different than
tss_create or pthread_key_create. Iirc, those dtors get called a lot
deeper in thread shutdown.
_______________________
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();
}
_______________________
I guess that is a hack.
[toc] | [prev] | [next] | [standalone]
| From | wij <wyniijj5@gmail.com> |
|---|---|
| Date | 2024-10-11 03:20 +0800 |
| Message-ID | <9dc17426ed663b192a4e1467b8fc488e6be377bd.camel@gmail.com> |
| In reply to | #120696 |
On Thu, 2024-10-10 at 12:04 -0700, Chris M. Thomasson wrote:
> On 10/10/2024 4:52 AM, jseigh wrote:
> > On 10/9/24 22:02, Chris M. Thomasson wrote:
> > > How can I get access to self in my ct_foo() function below? Am I
> > > misusing thread_local here? I want self to be able to be accessible
> > > from any function that my ct_thread() function calls. It feels like I
> > > am doing something wrong here. However, I am getting correct ctor's
> > > and dtor's, I am not sure how to access self from ct_foo() called from
> > > ct_thread()?
> > >
> >
> > std::thread::id id = std::this_thread::get_id();
> >
> > C tss uses a thread local array I think but I don't know
> > it gets notification on individual thread exits so it
> > can run the dtor. C++ has a cvar notify all on thread
> > exits.
> >
> > I have a different hack which I won't get around to
> > until I need it.
>
> I still don't know how C++ calls dtors wrt per-thread objects via
> thread_local. My hack would be to use tss_create and be done with it.
That's why thread in libwy (RAII) is implemented as a class, 'thread' is an overload
virtual function in the thread class Wy::Thread (inspired by old day Qt thread). And,
'thread local' mostly is data member of the Wy::PThread. Not many app. really need
'thread local' (Other threads can access the 'local thread data').
> However, this "works", but the only reason the dtor is being called has
> nothing to do with thread_local. I resorted to using thread_local for a
> pointer to a local per_thread object on the threads stack. It's dtor
> means the thread exited. However, this is a lot different than
> tss_create or pthread_key_create. Iirc, those dtors get called a lot
> deeper in thread shutdown.
Yes, RRAD is a problem of multi-threaded program.
> _______________________
> 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();
> }
> _______________________
>
>
> I guess that is a hack.
[toc] | [prev] | [next] | [standalone]
| From | "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> |
|---|---|
| Date | 2024-10-20 19:12 -0700 |
| Message-ID | <vf4db2$l6cb$6@dont-email.me> |
| In reply to | #120697 |
On 10/10/2024 12:20 PM, wij wrote:
[...]
Check this shit out:
Here is my crude code:
_______________________________________
#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;
}
_______________________________________
[toc] | [prev] | [next] | [standalone]
| From | wij <wyniijj5@gmail.com> |
|---|---|
| Date | 2024-10-21 19:06 +0800 |
| Message-ID | <324eb5eb8a81aa1109d9ff6cf05c29886569e368.camel@gmail.com> |
| In reply to | #120746 |
On Sun, 2024-10-20 at 19:12 -0700, Chris M. Thomasson wrote:
> On 10/10/2024 12:20 PM, wij wrote:
> [...]
>
> Check this shit out:
>
> Here is my crude code:
> _______________________________________
> #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;
> }
> _______________________________________
Don't know what the program is demonstrating.
The following is what I thought what ct_thread is doing.
1. In libwy, each thread is represented by a PThread object, following the RAII
principle. (your program looked to me std::thread API has no idea of RAII).
2. libwy only has Mutex (wrapper of pthread_mutex), because other kinds of
mutexes are considered not often used and easily implemented (except futex)
and many application variations, i.e. read/write mutex.
3. Wy::PThread has no public facilities for 'thread specific' (thread local),
because in most cases, 'thread local' can be implemented as class data
member.
(Wy::PThread is old from the time the underlying Linux Thread is buggy,
and I am tired to revise it without real cases showing the necessarity.)
// t.cpp
#include <Wy.pthread.h>
#include <Wy.stdio.h>
using namespace Wy;
class Thread : public PThread {
Mutex *m_gmtx; // for 'thread common' data
int m_id;
// Many 'thread local' data can be put here.
public:
Thread(Mutex& mtx, int id) : m_gmtx(&mtx), m_id(id) {};
Errno begin() override { return PThread::begin(); };
~Thread() {
NoCancel noc;
tmain_close(); // Stop possibly active thread, then destruction begins.
cout << "~Thread(mtx," << m_id << ")" WY_ENDL;
};
protected:
Errno tmain() override {
Mutex::Lock aa(*m_gmtx);
cout << "Thread(mtx," << m_id << ")" WY_ENDL;
return Ok;
};
};
#define CT_THREADS 5
int main()
try {
Errno r;
Mutex shared_mutex;
Thread thrd[CT_THREADS]={
Thread(shared_mutex,0),
Thread(shared_mutex,1),
Thread(shared_mutex,2),
Thread(shared_mutex,3),
Thread(shared_mutex,4),
};
for(int i=0; i<CT_THREADS; ++i) {
if((r=thrd[i].begin())!=Ok) {
WY_THROW(r);
}
}
for(int i=0; i<CT_THREADS; ++i) {
if((r=thrd[i].wait_if(PThread::Running))!=Ok) {
WY_THROW(r);
}
}
return 0;
}
catch(const Errno& e) {
cerr << wrd(e) << WY_ENDL;
throw;
}
catch(...) {
cerr << "Unknown thrown object" WY_ENDL;
throw;
};
-----------------------------
[]$ g++ t.cpp -lwy
[]$ ./a.out
Thread(mtx,0)
Thread(mtx,1)
Thread(mtx,4)
Thread(mtx,2)
Thread(mtx,3)
~Thread(mtx,4)
~Thread(mtx,3)
~Thread(mtx,2)
~Thread(mtx,1)
~Thread(mtx,0)
[toc] | [prev] | [next] | [standalone]
| From | wij <wyniijj5@gmail.com> |
|---|---|
| Date | 2024-10-21 19:20 +0800 |
| Message-ID | <4ba69cbf99403319abd6209d4423dabfebec0600.camel@gmail.com> |
| In reply to | #120747 |
On Mon, 2024-10-21 at 19:06 +0800, wij wrote:
> On Sun, 2024-10-20 at 19:12 -0700, Chris M. Thomasson wrote:
> > On 10/10/2024 12:20 PM, wij wrote:
> > [...]
> >
> > Check this shit out:
> >
> > Here is my crude code:
> > _______________________________________
> > #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;
> > }
> > _______________________________________
>
> Don't know what the program is demonstrating.
> The following is what I thought what ct_thread is doing.
>
> 1. In libwy, each thread is represented by a PThread object, following the RAII
> principle. (your program looked to me std::thread API has no idea of RAII).
> 2. libwy only has Mutex (wrapper of pthread_mutex), because other kinds of
> mutexes are considered not often used and easily implemented (except futex)
> and many application variations, i.e. read/write mutex.
> 3. Wy::PThread has no public facilities for 'thread specific' (thread local),
> because in most cases, 'thread local' can be implemented as class data
> member.
> (Wy::PThread is old from the time the underlying Linux Thread is buggy,
> and I am tired to revise it without real cases showing the necessarity.)
>
> // t.cpp
> #include <Wy.pthread.h>
> #include <Wy.stdio.h>
>
> using namespace Wy;
>
> class Thread : public PThread {
> Mutex *m_gmtx; // for 'thread common' data
> int m_id;
>
> // Many 'thread local' data can be put here.
> public:
> Thread(Mutex& mtx, int id) : m_gmtx(&mtx), m_id(id) {};
> Errno begin() override { return PThread::begin(); };
> ~Thread() {
> NoCancel noc;
> tmain_close(); // Stop possibly active thread, then destruction begins.
> cout << "~Thread(mtx," << m_id << ")" WY_ENDL;
> };
>
> protected:
> Errno tmain() override {
> Mutex::Lock aa(*m_gmtx);
> cout << "Thread(mtx," << m_id << ")" WY_ENDL;
> return Ok;
> };
> };
>
> #define CT_THREADS 5
>
> int main()
> try {
> Errno r;
> Mutex shared_mutex;
> Thread thrd[CT_THREADS]={
> Thread(shared_mutex,0),
> Thread(shared_mutex,1),
> Thread(shared_mutex,2),
> Thread(shared_mutex,3),
> Thread(shared_mutex,4),
> };
>
> for(int i=0; i<CT_THREADS; ++i) {
> if((r=thrd[i].begin())!=Ok) {
> WY_THROW(r);
> }
> }
>
> for(int i=0; i<CT_THREADS; ++i) {
> if((r=thrd[i].wait_if(PThread::Running))!=Ok) {
> WY_THROW(r);
> }
> }
>
> return 0;
> }
> catch(const Errno& e) {
> cerr << wrd(e) << WY_ENDL;
> throw;
> }
> catch(...) {
> cerr << "Unknown thrown object" WY_ENDL;
> throw;
> };
>
> -----------------------------
> []$ g++ t.cpp -lwy
> []$ ./a.out
> Thread(mtx,0)
> Thread(mtx,1)
> Thread(mtx,4)
> Thread(mtx,2)
> Thread(mtx,3)
> ~Thread(mtx,4)
> ~Thread(mtx,3)
> ~Thread(mtx,2)
> ~Thread(mtx,1)
> ~Thread(mtx,0)
Correction of the critic "your program looked to me std::thread API has no idea of RAII"
from "threads[i] = std::thread(ct_thread, std::ref(shared), i);"
It may just difference of implementation.
[toc] | [prev] | [next] | [standalone]
| From | jseigh <jseigh_es00@xemaps.com> |
|---|---|
| Date | 2024-10-10 15:57 -0400 |
| Message-ID | <ve9bjm$39fjg$1@dont-email.me> |
| In reply to | #120696 |
On 10/10/24 15:04, Chris M. Thomasson wrote:
> On 10/10/2024 4:52 AM, jseigh wrote:
>> On 10/9/24 22:02, Chris M. Thomasson wrote:
>>> How can I get access to self in my ct_foo() function below? Am I
>>> misusing thread_local here? I want self to be able to be accessible
>>> from any function that my ct_thread() function calls. It feels like I
>>> am doing something wrong here. However, I am getting correct ctor's
>>> and dtor's, I am not sure how to access self from ct_foo() called
>>> from ct_thread()?
>>>
>>
>> std::thread::id id = std::this_thread::get_id();
>>
>> C tss uses a thread local array I think but I don't know
>> it gets notification on individual thread exits so it
>> can run the dtor. C++ has a cvar notify all on thread
>> exits.
>>
>> I have a different hack which I won't get around to
>> until I need it.
>
> I still don't know how C++ calls dtors wrt per-thread objects via
> thread_local. My hack would be to use tss_create and be done with it.
>
I got some stack traces from C vs C++ threads (not the main threads
which have identical stack traces).
C
#4 0x00000000004011c6 in test ()
#5 0x00007ff68a489be1 in start_thread (arg=<optimized out>) at
pthread_create.c:440
#6 0x00007ff68a50ed40 in clone3 () at
../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
C++
#4 0x00000000004012b6 in test(void*) ()
#5 0x00007fa23f4dbad4 in std::execute_native_thread_routine
(__p=0x173ceb0) at ../../../../../libstdc++-v3/src/c++11/thread.cc:82
#6 0x00007fa23f089d22 in start_thread (arg=<optimized out>) at
pthread_create.c:443
#7 0x00007fa23f10ed40 in clone3 () at
../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
So the tss uses the pthread stuff and C++ uses
std::execute_native_thread_routine on top of that.
C and C++ main
#4 0x0000000000401056 in main ()
#5 0x00007fae98e295d0 in __libc_start_call_main
(main=main@entry=0x401050 <main>, argc=argc@entry=1,
argv=argv@entry=0x7ffff84f1d18) at
../sysdeps/nptl/libc_start_call_main.h:58
#6 0x00007fae98e29680 in __libc_start_main_impl (main=0x401050 <main>,
argc=1, argv=0x7ffff84f1d18, init=<optimized out>,
fini=<optimized out>, rtld_fini=<optimized out>,
stack_end=0x7ffff84f1d08) at ../csu/libc-start.c:389
#7 0x0000000000401085 in _start ()
No extra c++ magic sauce there that I can see.
I wonder how many people realize a thread's entry point isn't
necessarily the first thing that's called.
Joe Seigh
[toc] | [prev] | [next] | [standalone]
| From | "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> |
|---|---|
| Date | 2024-10-11 13:36 -0700 |
| Message-ID | <vec27j$3purf$1@dont-email.me> |
| In reply to | #120698 |
On 10/10/2024 12:57 PM, jseigh wrote: >> On 10/10/24 15:04, Chris M. Thomasson wrote: [...] > I wonder how many people realize a thread's entry point isn't > necessarily the first thing that's called. No shit! Good one. :^)
[toc] | [prev] | [next] | [standalone]
| From | jseigh <jseigh_es00@xemaps.com> |
|---|---|
| Date | 2024-10-11 09:50 -0400 |
| Message-ID | <vebael$3make$1@dont-email.me> |
| In reply to | #120696 |
On 10/10/24 15:04, Chris M. Thomasson wrote: > On 10/10/2024 4:52 AM, jseigh wrote: >> On 10/9/24 22:02, Chris M. Thomasson wrote: >>> How can I get access to self in my ct_foo() function below? Am I >>> misusing thread_local here? I want self to be able to be accessible >>> from any function that my ct_thread() function calls. It feels like I >>> am doing something wrong here. However, I am getting correct ctor's >>> and dtor's, I am not sure how to access self from ct_foo() called >>> from ct_thread()? >>> >> >> std::thread::id id = std::this_thread::get_id(); >> >> C tss uses a thread local array I think but I don't know >> it gets notification on individual thread exits so it >> can run the dtor. C++ has a cvar notify all on thread >> exits. >> >> I have a different hack which I won't get around to >> until I need it. > > I still don't know how C++ calls dtors wrt per-thread objects via > thread_local. My hack would be to use tss_create and be done with it. > Ok, it looks like they are using libc atexit function (stdlib.h) to invoke tls cleanup on thread exit. Joe Seigh
[toc] | [prev] | [next] | [standalone]
| From | "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> |
|---|---|
| Date | 2024-10-11 12:21 -0700 |
| Message-ID | <vebtr6$3p9aj$1@dont-email.me> |
| In reply to | #120702 |
On 10/11/2024 6:50 AM, jseigh wrote:
> On 10/10/24 15:04, Chris M. Thomasson wrote:
>> On 10/10/2024 4:52 AM, jseigh wrote:
>>> On 10/9/24 22:02, Chris M. Thomasson wrote:
>>>> How can I get access to self in my ct_foo() function below? Am I
>>>> misusing thread_local here? I want self to be able to be accessible
>>>> from any function that my ct_thread() function calls. It feels like
>>>> I am doing something wrong here. However, I am getting correct
>>>> ctor's and dtor's, I am not sure how to access self from ct_foo()
>>>> called from ct_thread()?
>>>>
>>>
>>> std::thread::id id = std::this_thread::get_id();
>>>
>>> C tss uses a thread local array I think but I don't know
>>> it gets notification on individual thread exits so it
>>> can run the dtor. C++ has a cvar notify all on thread
>>> exits.
>>>
>>> I have a different hack which I won't get around to
>>> until I need it.
>>
>> I still don't know how C++ calls dtors wrt per-thread objects via
>> thread_local. My hack would be to use tss_create and be done with it.
>>
>
> Ok, it looks like they are using libc atexit function (stdlib.h) to
> invoke tls cleanup on thread exit.
Thanks for all of the info Joe! I have an idea that might allow me to
get a dtor called from thread_local in my test code. I just need to try
it. Wrt:
_________________________________
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();
}
_________________________________
Well, what if I made self in ct_thread thread_local as well? That should
call self's dtor deeper down the pipe, so to speak. Humm... It should
work fine. Then any function that ct_thread calls would be able to
access self via g_per_thread...
;^)
[toc] | [prev] | [next] | [standalone]
| From | "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> |
|---|---|
| Date | 2024-10-11 22:53 -0700 |
| Message-ID | <ved2sr$28p8$2@dont-email.me> |
| In reply to | #120703 |
On 10/11/2024 12:21 PM, Chris M. Thomasson wrote:
> On 10/11/2024 6:50 AM, jseigh wrote:
>> On 10/10/24 15:04, Chris M. Thomasson wrote:
>>> On 10/10/2024 4:52 AM, jseigh wrote:
>>>> On 10/9/24 22:02, Chris M. Thomasson wrote:
>>>>> How can I get access to self in my ct_foo() function below? Am I
>>>>> misusing thread_local here? I want self to be able to be accessible
>>>>> from any function that my ct_thread() function calls. It feels like
>>>>> I am doing something wrong here. However, I am getting correct
>>>>> ctor's and dtor's, I am not sure how to access self from ct_foo()
>>>>> called from ct_thread()?
>>>>>
>>>>
>>>> std::thread::id id = std::this_thread::get_id();
>>>>
>>>> C tss uses a thread local array I think but I don't know
>>>> it gets notification on individual thread exits so it
>>>> can run the dtor. C++ has a cvar notify all on thread
>>>> exits.
>>>>
>>>> I have a different hack which I won't get around to
>>>> until I need it.
>>>
>>> I still don't know how C++ calls dtors wrt per-thread objects via
>>> thread_local. My hack would be to use tss_create and be done with it.
>>>
>>
>> Ok, it looks like they are using libc atexit function (stdlib.h) to
>> invoke tls cleanup on thread exit.
>
> Thanks for all of the info Joe! I have an idea that might allow me to
> get a dtor called from thread_local in my test code. I just need to try
> it. Wrt:
> _________________________________
>
> 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();
> }
> _________________________________
>
>
> Well, what if I made self in ct_thread thread_local as well? That should
> call self's dtor deeper down the pipe, so to speak. Humm... It should
> work fine. Then any function that ct_thread calls would be able to
> access self via g_per_thread...
>
> ;^)
Humm... It would still not be "dynamic" like the pthread and/or
tss_create version.
[toc] | [prev] | [next] | [standalone]
| From | "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> |
|---|---|
| Date | 2024-10-11 22:56 -0700 |
| Message-ID | <ved325$28pd$1@dont-email.me> |
| In reply to | #120706 |
On 10/11/2024 10:53 PM, Chris M. Thomasson wrote: [...] > Humm... It would still not be "dynamic" like the pthread and/or > tss_create version. It might be good enough for my purposes...
[toc] | [prev] | [next] | [standalone]
| From | jseigh <jseigh_es00@xemaps.com> |
|---|---|
| Date | 2024-10-11 17:05 -0400 |
| Message-ID | <vec3ut$3q5q6$1@dont-email.me> |
| In reply to | #120702 |
On 10/11/24 09:50, jseigh wrote: > On 10/10/24 15:04, Chris M. Thomasson wrote: >> On 10/10/2024 4:52 AM, jseigh wrote: >>> On 10/9/24 22:02, Chris M. Thomasson wrote: >>>> How can I get access to self in my ct_foo() function below? Am I >>>> misusing thread_local here? I want self to be able to be accessible >>>> from any function that my ct_thread() function calls. It feels like >>>> I am doing something wrong here. However, I am getting correct >>>> ctor's and dtor's, I am not sure how to access self from ct_foo() >>>> called from ct_thread()? >>>> >>> >>> std::thread::id id = std::this_thread::get_id(); >>> >>> C tss uses a thread local array I think but I don't know >>> it gets notification on individual thread exits so it >>> can run the dtor. C++ has a cvar notify all on thread >>> exits. >>> >>> I have a different hack which I won't get around to >>> until I need it. >> >> I still don't know how C++ calls dtors wrt per-thread objects via >> thread_local. My hack would be to use tss_create and be done with it. >> > > Ok, it looks like they are using libc atexit function (stdlib.h) to > invoke tls cleanup on thread exit. Correction. atexit is for process exit not thread exit. So those start_ functions are where they are doing the exit handling. Joe Seigh
[toc] | [prev] | [next] | [standalone]
| From | "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> |
|---|---|
| Date | 2024-10-12 13:44 -0700 |
| Message-ID | <veen2q$a82s$2@dont-email.me> |
| In reply to | #120705 |
On 10/11/2024 2:05 PM, jseigh wrote: > On 10/11/24 09:50, jseigh wrote: >> On 10/10/24 15:04, Chris M. Thomasson wrote: >>> On 10/10/2024 4:52 AM, jseigh wrote: >>>> On 10/9/24 22:02, Chris M. Thomasson wrote: >>>>> How can I get access to self in my ct_foo() function below? Am I >>>>> misusing thread_local here? I want self to be able to be accessible >>>>> from any function that my ct_thread() function calls. It feels like >>>>> I am doing something wrong here. However, I am getting correct >>>>> ctor's and dtor's, I am not sure how to access self from ct_foo() >>>>> called from ct_thread()? >>>>> >>>> >>>> std::thread::id id = std::this_thread::get_id(); >>>> >>>> C tss uses a thread local array I think but I don't know >>>> it gets notification on individual thread exits so it >>>> can run the dtor. C++ has a cvar notify all on thread >>>> exits. >>>> >>>> I have a different hack which I won't get around to >>>> until I need it. >>> >>> I still don't know how C++ calls dtors wrt per-thread objects via >>> thread_local. My hack would be to use tss_create and be done with it. >>> >> >> Ok, it looks like they are using libc atexit function (stdlib.h) to >> invoke tls cleanup on thread exit. > > Correction. atexit is for process exit not thread exit. So those > start_ functions are where they are doing the exit handling. Well, here is a call stack for my most recent try wrt tss dtors in pure C++: > ct_plot_pre_alpha.exe!ct_per_thread::~ct_per_thread() Line 39 C++ ct_plot_pre_alpha.exe!`ct_thread'::`3'::`dynamic atexit destructor for 'self''() C++ ct_plot_pre_alpha.exe!__dyn_tls_dtor(void * __formal, const unsigned long dwReason, void * __formal) Line 122 C++ ntdll.dll!00007ffbed20bfca() Unknown ntdll.dll!00007ffbed1b8b5f() Unknown ntdll.dll!00007ffbed1b97e9() Unknown ntdll.dll!00007ffbed1b9428() Unknown ntdll.dll!00007ffbed1eaf7e() Unknown KernelBase.dll!00007ffbea68e1ca() Unknown ucrtbased.dll!00007ffb83e932cb() Unknown ucrtbased.dll!00007ffb83e93931() Unknown ucrtbased.dll!00007ffb83e93017() Unknown kernel32.dll!00007ffbec15257d() Unknown ntdll.dll!00007ffbed1eaf08() Unknown atexit is in there.
[toc] | [prev] | [next] | [standalone]
Page 1 of 2 [1] 2 Next page →
Back to top | Article view | comp.lang.c++
csiph-web