
Hi, I have a couple of questions about thread_specific_ptr in 1.35. 1. I can see that there is an ability to set up a logic of reclaiming resources that thread_specific_ptr points to. Why is it limited (a) only to a pointer to function and (b) can only be set up in thread_specific_ptr's constructor? It might be better to provide interface similar to shared_ptr in this way? BTW, I can see that although the cleanup function is required to receive T* as its argument, it is being passed void* in the run_custom_cleanup_function. Isn't an explicit cast required here? 2. Why thread_specific_ptr isn't copyable? Why can't it share TLS keys (or whatever it uses to find the thread-specific resource) in the dynamically allocated cleanup object? I would expect thread_specific_ptr, being a pointer as its name implies, to be copyable.

Andrey Semashev <andysem <at> mail.ru> writes:
Hi, I have a couple of questions about thread_specific_ptr in 1.35.
1. I can see that there is an ability to set up a logic of reclaiming resources that thread_specific_ptr points to. Why is it limited (a) only to a pointer to function and (b) can only be set up in thread_specific_ptr's constructor? It might be better to provide interface similar to shared_ptr in this way?
a) The interface is how it was in boost 1.34 and prior. The underlying code would allow something more general, but I haven't updated the interface for that yet. b) If it wasn't set up in the constructor, it would have to be set up with every call to reset. There's arguments either way. This is the boost 1.34 (and prior) way.
BTW, I can see that although the cleanup function is required to receive T* as its argument, it is being passed void* in the run_custom_cleanup_function. Isn't an explicit cast required here?
Yes, you're right. I must have missed that when I copied into the boost tree, and there wasn't a test for that in boost. This is now issue 1665, which is fixed in version 43461 on trunk.
2. Why thread_specific_ptr isn't copyable? Why can't it share TLS keys (or whatever it uses to find the thread-specific resource) in the dynamically allocated cleanup object? I would expect thread_specific_ptr, being a pointer as its name implies, to be copyable.
Fundamentally, the TLS key is the instance of thread_specific_ptr itself, which is therefore not transferable. If you could assign one instance to another, how would that affect other threads that had values associated with the old value stored in that instance? If you can't change an object after construction, why do you need to copy it? You could just pass around a pointer or reference instead. Anthony -- Anthony Williams Just Software Solutions Ltd - http://www.justsoftwaresolutions.co.uk Registered in England, Company Number 5478976. Registered Office: 15 Carrallack Mews, St Just, Cornwall, TR19 7UL

Anthony Williams wrote:
Andrey Semashev <andysem <at> mail.ru> writes:
Hi, I have a couple of questions about thread_specific_ptr in 1.35.
1. I can see that there is an ability to set up a logic of reclaiming resources that thread_specific_ptr points to. Why is it limited (a) only to a pointer to function and (b) can only be set up in thread_specific_ptr's constructor? It might be better to provide interface similar to shared_ptr in this way?
a) The interface is how it was in boost 1.34 and prior. The underlying code would allow something more general, but I haven't updated the interface for that yet.
So I guess, I may hope this more general interface will be implemented? In 1.36, maybe? Thanks in advance. :)
b) If it wasn't set up in the constructor, it would have to be set up with every call to reset. There's arguments either way. This is the boost 1.34 (and prior) way.
Yes, and I see nothing bad with it. Shared_ptr does precisely the same, and since this cleanup function is intended to do the same as shared_ptr's deleter does, I don't see why would the interface have to differ. The only tricky thing here is that different threads could assign different cleanup functions to the same pointer. But that isn't very hard to implement, is it?
2. Why thread_specific_ptr isn't copyable? Why can't it share TLS keys (or whatever it uses to find the thread-specific resource) in the dynamically allocated cleanup object? I would expect thread_specific_ptr, being a pointer as its name implies, to be copyable.
Fundamentally, the TLS key is the instance of thread_specific_ptr itself, which is therefore not transferable. If you could assign one instance to another, how would that affect other threads that had values associated with the old value stored in that instance?
If there are no other pointers that own that TLS key, the cleanup functions for all threads are invoked, and the TLS key is released.
If you can't change an object after construction, why do you need to copy it? You could just pass around a pointer or reference instead.
I'm not sure I got you right here. My case is quite simple. I have a class that needs to store some data in TLS to avoid thread contention. And I want to keep the class copyable. Now I have to have a member "shared_ptr< thread_specific_ptr< MyData > >" to achieve that, and that's quite clumsy, not to mention the overhead of such solution.

Andrey Semashev <andysem <at> mail.ru> writes:
Anthony Williams wrote:
Andrey Semashev <andysem <at> mail.ru> writes: a) The interface is how it was in boost 1.34 and prior. The underlying code would allow something more general, but I haven't updated the interface for that yet.
So I guess, I may hope this more general interface will be implemented? In 1.36, maybe? Thanks in advance. :)
Quite possibly.
b) If it wasn't set up in the constructor, it would have to be set up with every call to reset. There's arguments either way. This is the boost 1.34 (and prior) way.
Yes, and I see nothing bad with it. Shared_ptr does precisely the same, and since this cleanup function is intended to do the same as shared_ptr's deleter does, I don't see why would the interface have to differ.
The only tricky thing here is that different threads could assign different cleanup functions to the same pointer. But that isn't very hard to implement, is it?
This isn't hard to do: the underlying code does this anyway.
2. Why thread_specific_ptr isn't copyable? Why can't it share TLS keys (or whatever it uses to find the thread-specific resource) in the dynamically allocated cleanup object? I would expect thread_specific_ptr, being a pointer as its name implies, to be copyable.
Fundamentally, the TLS key is the instance of thread_specific_ptr itself, which is therefore not transferable. If you could assign one instance to another, how would that affect other threads that had values associated with the old value stored in that instance?
If there are no other pointers that own that TLS key, the cleanup functions for all threads are invoked, and the TLS key is released.
How is this to be managed? Cleanup handlers for the TLS key need to run in the context of each thread, and only run when a thread changes its value with reset(), or when the thread exits. You cannot invoke a cleanup handler for another thread.
If you can't change an object after construction, why do you need to copy it? You could just pass around a pointer or reference instead.
I'm not sure I got you right here. My case is quite simple. I have a class that needs to store some data in TLS to avoid thread contention. And I want to keep the class copyable. Now I have to have a member "shared_ptr< thread_specific_ptr< MyData > >" to achieve that, and that's quite clumsy, not to mention the overhead of such solution.
The way the interface works at the moment, TLS data is implicitly associated with a specific thread_specific_ptr instance. It is fundamentally not copyable or movable, because doing so would invalidate the references to the instance from other threads. Using shared_ptr as you suggest is exactly the way to do this. The thread_specific_ptr is a bit clunky altogether. I intend to come up with something better in the future, but it may be moot once compilers start implementing the new thread_local storage duration. Anthony -- Anthony Williams Just Software Solutions Ltd - http://www.justsoftwaresolutions.co.uk Registered in England, Company Number 5478976. Registered Office: 15 Carrallack Mews, St Just, Cornwall, TR19 7UL

Yes, and I see nothing bad with it. Shared_ptr does precisely the same, and since this cleanup function is intended to do the same as shared_ptr's deleter does, I don't see why would the interface have to differ.
thread_specific_ptr was more or less intended to provide a C++ equivalent to pthread_setspecific, and the POSIX interface associates a single destructor function to a TSD slot. It's technically possible to emulate shared_ptr, but is there a reason for that?

Peter Dimov wrote:
Yes, and I see nothing bad with it. Shared_ptr does precisely the same, and since this cleanup function is intended to do the same as shared_ptr's deleter does, I don't see why would the interface have to differ.
thread_specific_ptr was more or less intended to provide a C++ equivalent to pthread_setspecific, and the POSIX interface associates a single destructor function to a TSD slot. It's technically possible to emulate shared_ptr, but is there a reason for that?
There is no specific reason for that. That feature would just follow from the interface if thread_specific_ptr was consistent with shared_ptr. As for the consistency itself, well, in my opinion this feature can potentially be useful if one would want to allocate memory for TSD in a thread-specific allocator or storage. This may be useful for both performance optimization and memory consumption monitoring and control on per-thread basis. Besides, overall interface consistency is a sign of a good library design.

Andrey Semashev:
Peter Dimov wrote: ...
thread_specific_ptr was more or less intended to provide a C++ equivalent to pthread_setspecific, and the POSIX interface associates a single destructor function to a TSD slot. It's technically possible to emulate shared_ptr, but is there a reason for that?
There is no specific reason for that. That feature would just follow from the interface if thread_specific_ptr was consistent with shared_ptr.
But it's not (semantically) consistent with shared_ptr at all. Making it syntactically consistent will just mislead more people. One could even argue that it needs to be made even less syntactically consistent with a smart pointer than it is now, because it's not like other smart pointers at all. Depending on how one looks at it, it represents either a TSD key or an auto_ptr with a thread-local storage class.
Besides, overall interface consistency is a sign of a good library design.
Good use of consistency is a sign of good design. Consistency alone is not.

Peter Dimov wrote:
Andrey Semashev:
Peter Dimov wrote: ....
There is no specific reason for that. That feature would just follow from the interface if thread_specific_ptr was consistent with shared_ptr.
But it's not (semantically) consistent with shared_ptr at all. Making it syntactically consistent will just mislead more people. One could even argue that it needs to be made even less syntactically consistent with a smart pointer than it is now, because it's not like other smart pointers at all. Depending on how one looks at it, it represents either a TSD key or an auto_ptr with a thread-local storage class.
Agreed. And I for one would personally prefer to see a thread_specific_value instead of thread_specific_ptr in this case. But since we have a pointer here, I would like it to behave as a pointer. And if it has a feature that is similar to other well known pointers, I would like the feature to have a similar way of use. But that's my HO, after all.

Andrey Semashev: ...
Agreed. And I for one would personally prefer to see a thread_specific_value instead of thread_specific_ptr in this case.
My favorite spelling is thread_specific< X > tx; It can be made to work (I even had implemented it halfway once) but the problematic case is how to handle dynamically loaded (and unloaded) libraries with global/static thread_specific<> variables. The shared_ptr-like deleter functionality is in this case simply spelled thread_specific< shared_ptr<X> > tpx; Depending on how the thread_local storage class ends up being specified, we might still need to implement thread_specific at some point.

Peter Dimov wrote:
Andrey Semashev: ....
Agreed. And I for one would personally prefer to see a thread_specific_value instead of thread_specific_ptr in this case.
My favorite spelling is
thread_specific< X > tx;
It can be made to work (I even had implemented it halfway once) but the problematic case is how to handle dynamically loaded (and unloaded) libraries with global/static thread_specific<> variables.
I had done the same thing too (ironically, I used the same spelling as you suggest), but I only needed to store void* sized POD types, so it did not make any dynamic allocations and there were no problems with dlls. In fact, I think such a simple and straightforward tool could be used as a basic building block for a more advanced tool.
The shared_ptr-like deleter functionality is in this case simply spelled
thread_specific< shared_ptr<X> > tpx;
Depending on how the thread_local storage class ends up being specified, we might still need to implement thread_specific at some point.
AFAIR, the C++0x proposal is a new data storage type. Therefore we won't be able to create TLS class members. This is where thread_specific could come into play.

Anthony Williams wrote:
Andrey Semashev <andysem <at> mail.ru> writes:
2. Why thread_specific_ptr isn't copyable? Why can't it share TLS keys (or whatever it uses to find the thread-specific resource) in the dynamically allocated cleanup object? I would expect thread_specific_ptr, being a pointer as its name implies, to be copyable. Fundamentally, the TLS key is the instance of thread_specific_ptr itself, which is therefore not transferable. If you could assign one instance to another, how would that affect other threads that had values associated with the old value stored in that instance? If there are no other pointers that own that TLS key, the cleanup functions for all threads are invoked, and the TLS key is released.
How is this to be managed? Cleanup handlers for the TLS key need to run in the context of each thread, and only run when a thread changes its value with reset(), or when the thread exits. You cannot invoke a cleanup handler for another thread.
Hmm, I missed that. But how does it work now? I mean, how the cleanup handlers are called in the current implementation when the thread_specific_ptr is destroyed? You should call them in context of their threads anyway.
The thread_specific_ptr is a bit clunky altogether. I intend to come up with something better in the future, but it may be moot once compilers start implementing the new thread_local storage duration.
I think, efforts on thread_specific_ptr are not completely worthless. Even after C++0x is out and supported by the most wide-spread compilers we will still have to write backwards-compatible code to support older compilers for some time. MSVC 6 support is an example of such evolution history.

Andrey Semashev <andysem <at> mail.ru> writes:
Anthony Williams wrote:
How is this to be managed? Cleanup handlers for the TLS key need to run in the context of each thread, and only run when a thread changes its value with reset(), or when the thread exits. You cannot invoke a cleanup handler for another thread.
Hmm, I missed that. But how does it work now? I mean, how the cleanup handlers are called in the current implementation when the thread_specific_ptr is destroyed? You should call them in context of their threads anyway.
The cleanup handler is called in the context of each thread when the thread exits --- the cleanup handler itself is reference counted. If you destroy a thread_specific_ptr, and then access it from any thread (whether to get or set the value), then the behaviour is undefined, as for any object accessed after its destruction.
The thread_specific_ptr is a bit clunky altogether. I intend to come up with something better in the future, but it may be moot once compilers start implementing the new thread_local storage duration.
I think, efforts on thread_specific_ptr are not completely worthless. Even after C++0x is out and supported by the most wide-spread compilers we will still have to write backwards-compatible code to support older compilers for some time. MSVC 6 support is an example of such evolution history.
Agreed. Anthony -- Anthony Williams Just Software Solutions Ltd - http://www.justsoftwaresolutions.co.uk Registered in England, Company Number 5478976. Registered Office: 15 Carrallack Mews, St Just, Cornwall, TR19 7UL
participants (3)
-
Andrey Semashev
-
Anthony Williams
-
Peter Dimov