Saturday, December 10, 2005

Thread return value/threadID/ handle/ join/detach vs C++

Two entries ago, I opined that in a C++ program there should be an object that represents every thread. One interesting consequence of this statement is that many of the "features" of the OS-supplied multithreading support become unnecesary and even counterproductive.

For example, on almost all platforms, start_thread (by whatever name) calls a function with a void* argument. That's ok. Amost all thread functions begin life with a cast. However the thread function is expected to return a value: usually an int but maybe a DWORD or even a void * or whatever depending on your platform. In C++ the proper return value from this function should be 0 -- always! (Actually it should be void, but it seems a shame to disappoint the OS that's eagerly awating the zero. (And besides the compiler won't let me get away with it.))

Why?

Because if there is an object associated with the thread, the thread has a much richer channel through which to return information -- the members of the object.

And speaking of return values, many times no one cares what the thread has to say as it exits. [No death-bed epigrams for you, Thread, you're outta here. ] The joinable vs detached concept in many OSs accomodates this desire on the part of the thread to get the last word in.

However, since the thread now has a whole object available through which to return values, and since anyone who cares can keep a smart pointer to the object (remember the purpose of smart pointers is to manage object lifetimes.) the whole joinable vs. detached issue becomes moot.

Threads in C++ should ALWAYS run detached. Rather than joining a thread, you can wait on a condition in the thread's object (safely because you have a smart ptr to guarantee the condition will be around to be waited on.)

This approach might be much kinder to your system resources. Many OS's hang on to lots of information about a terminated thread -- possibly even it's entire stack, register safe storage, open FDs, etc. waiting for someone to join in and tell them the resources can be freed. Using an object can be a considerable savings.

Which brings up the issue of thread ID's If your interaction with the thread is via the object, you don't need a thread ID to join the thread -- and you certainly don't need a thread ID to kill the thread (see blog entry n-1) so the thread ID becomes much less valuable. It still has some value in identifying the thread in log messages (Ever tried to follow a log that didn't include thread ID's in a message? Me too, and I still regret it.) And the thread ID might also be involved in managing thread specific storage -- although many uses of TSS could be handled better by storing the data (or pointers thereto) in the thread's object.

Speaking of TSS. Think of it as FORTRAN COMMON for the thread-wielding-crowd. There's usually a better way, but sometimes the better way requires some thought <insert cynical comment here.>

3 comments:

Anonymous said...

Hi Dale,

I only just today came across your blog and it made really interesting reading...atleast for a proggie whos just getting his feet wet with C++.

Coming from a Java background, the most difficult thing I find in C++ is choosing libraries!

What would be your recommendation for a good cross-platform threads library - go for ACE or Boost? You have made use of both...how about publishing a comparison between both? :-)

Regards,
Tarun

Dale Wilson said...

Interesting question. The (relatively) short answer is:

The thread support in boost is just not very well thought out. I feel really uncomfortable with a model in which one object "applies threadness" to another one.

ACE provides an amazing degree of platform independence. However it's pretty heavy handed -- it's hard to pick and choose the pieces of ACE (aargh, Matey) that are appropriate to your application. It's best if you happen to be writing the application for which ACE was designed (passing around buffers of data on a network.)

Personally, I also have a lot of trouble with some of the ACE names for things. For example, an ACE_Task is a useful thingie -- it's just not what I think of when I hear the word "task."

That being said, I have yet to find a threading library better than ACE.

Anyone want to take that as a challenge?

Anonymous said...

wxWidgets library has quite pretty thread support and it's a very portable library. It can be compiled in some tiny mode (so called wxBase) in which no GUI support is included. My recommendation (almost 10 years programming experience).