27

I was commenting on an answer that thread-local storage is nice and recalled another informative discussion about exceptions where I supposed

The only special thing about the execution environment within the throw block is that the exception object is referenced by rethrow.

Putting two and two together, wouldn't executing an entire thread inside a function-catch-block of its main function imbue it with thread-local storage?

It seems to work fine, albeit slowly. Is this novel or well-characterized? Is there another way of solving the problem? Was my initial premise correct? What kind of overhead does get_thread incur on your platform? What's the potential for optimization?

#include <iostream> #include <pthread.h> using namespace std; struct thlocal { string name; thlocal( string const &n ) : name(n) {} }; struct thread_exception_base { thlocal &th; thread_exception_base( thlocal &in_th ) : th( in_th ) {} thread_exception_base( thread_exception_base const &in ) : th( in.th ) {} }; thlocal &get_thread() throw() { try { throw; } catch( thread_exception_base &local ) { return local.th; } } void print_thread() { cerr << get_thread().name << endl; } void *kid( void *local_v ) try { thlocal &local = * static_cast< thlocal * >( local_v ); throw thread_exception_base( local ); } catch( thread_exception_base & ) { print_thread(); return NULL; } int main() { thlocal local( "main" ); try { throw thread_exception_base( local ); } catch( thread_exception_base & ) { print_thread(); pthread_t th; thlocal kid_local( "kid" ); pthread_create( &th, NULL, &kid, &kid_local ); pthread_join( th, NULL ); print_thread(); } return 0; } 

This does require defining new exception classes derived from thread_exception_base, initializing the base with get_thread(), but altogether this doesn't feel like an unproductive insomnia-ridden Sunday morning…

EDIT: Looks like GCC makes three calls to pthread_getspecific in get_thread. EDIT: and a lot of nasty introspection into the stack, environment, and executable format to find the catch block I missed on the first walkthrough. This looks highly platform-dependent, as GCC is calling some libunwind from the OS. Overhead on the order of 4000 cycles. I suppose it also has to traverse the class hierarchy but that can be kept under control.

5
  • Is a function try/catch block allowed for main? Commented Mar 21, 2010 at 15:12
  • 1
    Most certainly—the standard specifies that main's catch block doesn't handle throws from global/static constructors. Not that it's essential to this mechanism. Commented Mar 21, 2010 at 15:17
  • What happens if a second exception is thrown? Commented Mar 21, 2010 at 16:14
  • @jdv: Every exception class needs to be derived from thlocal and that base always needs to be initialized with get_thread(). Hmm, sounds like I need an intermediate pointer in there to avoid copying data. Commented Mar 21, 2010 at 16:26
  • 2
    +1 This is an awesome hack and bit of lateral thinking. Commented Mar 26, 2010 at 11:13

4 Answers 4

11

In the playful spirit of the question, I offer this horrifying nightmare creation:

class tls { void push(void *ptr) { // allocate a string to store the hex ptr // and the hex of its own address char *str = new char[100]; sprintf(str, " |%x|%x", ptr, str); strtok(str, "|"); } template <class Ptr> Ptr *next() { // retrieve the next pointer token return reinterpret_cast<Ptr *>(strtoul(strtok(0, "|"), 0, 16)); } void *pop() { // retrieve (and forget) a previously stored pointer void *ptr = next<void>(); delete[] next<char>(); return ptr; } // private constructor/destructor tls() { push(0); } ~tls() { pop(); } public: static tls &singleton() { static tls i; return i; } void *set(void *ptr) { void *old = pop(); push(ptr); return old; } void *get() { // forget and restore on each access void *ptr = pop(); push(ptr); return ptr; } }; 

Taking advantage of the fact that according to the C++ standard, strtok stashes its first argument so that subsequent calls can pass 0 to retrieve further tokens from the same string, so therefore in a thread-aware implementation it must be using TLS.

example *e = new example; tls::singleton().set(e); example *e2 = reinterpret_cast<example *>(tls::singleton().get()); 

So as long as strtok is not used in the intended way anywhere else in the program, we have another spare TLS slot.

Sign up to request clarification or add additional context in comments.

3 Comments

strtok is defined in terms of a sequence of calls, so it's ambiguous whether a thread-aware implementation keeps a TLS pointer or a global with a mutex. I am horrified, though.
I'm glad it had the desired effect! :) If strtok was only protected by a mutex that would be locked for each call, that wouldn't really help, so TLS is the only likely solution. Though I suppose it could have some perverted use as a semi-reliable communication channel between threads! And on top of that I could build a stream interface, like TCP over IP... Now there's a challenge!
This is the code that haunts the demon that haunts the people in Paranormal Activity.
3

I think you're onto something here. This might even be a portable way to get data into callbacks that don't accept a user "state" variable, as you've mentioned, even apart from any explicit use of threads.

So it sounds like you've answered the question in your subject: YES.

Comments

0
void *kid( void *local_v ) try { thlocal &local = * static_cast< thlocal * >( local_v ); throw local; } catch( thlocal & ) { print_thread(); return NULL; } 

==

void *kid (void *local_v ) { print_thread(local_v); } 

I might be missing something here, but it's not a thread local storage, just unnecessarily complicated argument passing. Argument is different for each thread only because it is passed to pthread_create, not because of any exception juggling.


It turned out that I indeed was missing that GCC is producing actual thread local storage calls in this example. It actually makes the issue interesting. I'm still not quite sure whether it is a case for other compilers, and how is it different from calling thread storage directly.

I still stand by my general argument that the same data can be accessed in a more simple and straight-forward way, be it arguments, stack walking or thread local storage.

16 Comments

Thread local storage is just complicated argument passing. get_thread retrieves the argument from any unknown location up the call stack. pthread_create isn't special, cf main. The significance here is that it works for functions without arguments, such as destructors or ill-designed callbacks.
Exactly - but it doesn't work for function without arguments! You have to explicitly pass it to function by throw-ing before call. It's just that function(arguments) becomes throw arguments ... function.
I also don't think it's entirely correct to call pthread_getspecific "argument passing", no more than stack pointer itself.
@ima: print_thread is as without-arguments as functions get in C++. The intent is only one throw local; at the topmost scope. In terms of the functional language formalism, all data movement is argument passing. Can you give an example of something that should be possible but isn't?
One? Your example has 3 'throw local's, one for each call. Consider: you can declare function without arguments, and then push and pop arguments in stack manually - would it make a parameter-less function? Obviously no, just one with a custom calling convention. You are using exception objects as a custom stack implementation, and store parameters there. And why are we talking about functional language formalism here?
|
0

Accessing data on the current function call stack is always thread safe. That's why your code is thread safe, not because of the clever use of exceptions. Thread local storage allows us to store per-thread data and reference it outside of the immediate call stack.

6 Comments

Data on the call stack is quite unsafe if used in an unsafe manner. Thread local storage is not inherently safer than anything else, it's simply keyed by thread.
This has nothing to do with thread safety, only thread local storage.
If you declared a bit of data on the stack and then passed it around to every function as an extra argument, you'd effectively have thread-local storage. The advantage of TLS is that you don't have to add the extra argument. The clever thing about this idea is noting that you can say void foo() { throw; }, where throw has no local context to determine what it should be throwing, so the runtime has to keep one per call stack, which is to say, one per thread. Hence this is a way of sharing a value only within a thread without using parameters to pass it around. Just like TLS.
My original comment did contain a serious error which I consider a typo, but you be the judge. In any case I apologize for the confusion; I've corrected the error. I still don't understand how exceptions can take the place of thread local storage. There's no scenario I can thing of where passing stack variables to functions on or off the current thread will create per-thread variable instance, which is what thread local storage is.
This isn't stack variables. It's a storage slot provided by the C++ runtime library, which is used to store the most recently caught exception so that throw; will be able to retrieve it and rethrow it. The gag here is to store arbitrary data in an exception object, use throw/catch to get that exception stored in the slot, and then use throw; to retrieve it elsewhere. Hence it is a "global" storage slot...
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.