
Although Boost.Threads uses templates in expressing some of its capabilities, it is a relatively closed design wrt extension This is exactly the reason (well one of the reasons ;-) why we don't use it. Our code changes scheduling classes, thread priorities etc and whilst this was a planned extension to Boost.Threads it still isn't part of the released version - although this implies that Boost.Threads would be a poor starting point, as Kevlin points out it is the closed design that precludes wider adoption.
Considering first the question of the threading part, Boost.Threads is currently based on the idea that a thread is identified with an object that launches it. This notion is somewhat confused by the idea that on destruction the thread object is destroyed but the thread is not -- in other words the thread is not identified the thread object... except when it is. There needs to be a separation of concepts here
Yes, that's something that's always bothered me too. Having read through the history I can understand why Bill Kempf ended up with what he did - and having delved into the thread_dev branch I can also understand where he was heading and it's rather different to where it is today! If my understanding is correct the main reason for the default constrctor is to support operations on threads from other libraries, which doesn't seem very compelling imv. There is some value in the argument that it would also support operations on the primary thread but in many cases it's hard/impossible to do much to the primary thread anyway . . .
Another appropriate separation is the distinction between initialisation and execution. These are significantly different concepts but they are conflated in the existing thread-launching interface: the constructor is responsible both for preparing the thread and launching it, which means that it is not possible for one piece of code to set up a thread and another to initiate it separately at its own discretion, eg thread pools.
When I first saw Boost.Threads use of functors it was clearly better then anything I'd used before . . . Kevlins ideas take this one step further though, which seems to make sense.
The separation also offers a simple and non-intrusive avenue for platform-specific extension of how a thread is to execute: configuration details such as scheduling policy, stack size, security attributes, etc, can be added as constructors without intruding on the signatures of any other function in the threading interface:
size_t stack_size = ...; security_attributes security(...); thread async_function(stack_size, security);
Via the joiner the initiator can poll or wait for the completion of the running thread, and control it in other ways -- eg request cancellation of the task (should we chose to attempt standardising in some form this often contentious and challenging capability).
The joiner would be a copyable, assignable and default constructible handle, and as its principal action the act of joining can be expressed as a function-call:
joiner wait = run(first_task); ... wait(); But if I wanted to store a joiner in an stl container then I might need comparison operators too. The thread_dev branch shows that Bill Kempf had worked really hard to get a 'thread' object (meaning an object that can manipulate and join a running thread) that supported
That's good. <snip explanation of joiner> this. I've never needed this but the archives suggest that some people do ;-)
If there are no joiners for a given thread, that thread is detached <snip> It seems to have become habit to discard [the return] value in more OO interfaces, giving a result of void.
Conceptually a detached thread with no joiner sounds good, however I've never actually investigated whether a detached thread concept is portable - does an application with a detached posix thread behave the same way as one with a detached WIN32 thread? This might be important, since Kevlin suggests that many thread libraries ignore the return value (I agree) and that may imply that it's accepted practice to ignore the return value and therefore many developers porting code to this model might by default create detached threads (whether intentional or not, this would be the result of ignoring the return value). Which if the behaviour is different might be bad :-(
This leads to a threader interface like the following:
class threader { public: threader(); template<typename nullary_function> joiner<result_of<nullary_function>::type> operator()(nullary_function); ... };
It's arguable whether the threader object provides much additonal value though, since it's sole purpose seems to be to provide a placeholder for the 'launch' function. So every time I want to start a new thread I have to do: int task(); ... threader launcher; joiner<int> result = launcher(task); The obvious (and therefore no doubt flawed) alternative is: joiner<int> result = launch(task); Boost.Threads use of the constructor to launch the thread is no better imv. On second thoughts a threader does make sense some since it also provides a placeholder for modifying the behaviour by providing an alternative constructor.
joiner<void> wait_for_completion = thread(void_task); joiner<int> wait_for_value = thread(int_task);
I still read this as a noun. Something like 'launch' might make the action clearer and avoids possible confusion with the operating system definition of 'thread' (which is a noun).
Now, in terms of synchronisation the Boost.Threads library offers a number of primitives, such as mutexes, but unfortunately couples them to a relatively closed programming model. The synchronisation primitives provided do not lead to a general model for locking and force the user into using resource acquisition objects when this is not always the best solution. Given that C++ should aim to be the language and API of choice for systems-level work, the restriction of a mandatory scoped-locking interface does not seem appropriate.
Agreed again. It feels too protective and probably won't prevent deadlock, whereas (for example) externally locking might. The thread_dev branch of Boost.Threads contained a lot of work in progress. Although Michael Glassford and others have done a great job to implement some of this, some key parts (copyable 'thread handles', cancellation etc) have not made it to release so we already know that there is a better version of Boost.Thread than the current one - changing to copyable thread handles is a big change which further suggests that it's a good reason not to standardise the current version of Boost.Thread. I've studied the thread_dev branch in some detail and I can see why Bill Kempf chose to use thread local storage to store thread state and I can see why he chose to use macros to exclude/include thread priority functions. This does create a whole lot of other problems though (e.g. TLS cleanup, solved for some platforms but not all) which would create a rather 'heavyweight' solution that doesn't always seem appropriate. Conclusion: Boost.Threads is a big step forward conceptually but isn't quite flexible enough and is also unfinished. It's not suitable for the majority of our applications in it's current form although as Kevlin points out it wouldn't take a huge shift to provide a more useful starting point. Malcolm Noyes