Much of my last week has been tweaking my thread stuff!
Trash: the thread graveyard
I wanted to re-emphasize how useful that thread graveyard is in practice.
I don’t allow any raw pointers in my code at all - I use patterns like this:[code]thread_ptr thread(new SomeClass()); // ptr and thread_ptr are my ScopedPtr variants…
if (!doSomethingPerhapsStartThread(thread.get()))
return NULL; // We got an error.
// If I get an error here, thread goes into the graveyard if it’s running!
if (!doSomethingElse(thread.get()))
return NULL;
return data.transfer();[/code]If I don’t have my “thread_ptr”, which puts threads into the thread graveyard and then deletes them when they’ve stopped, I’ve got to think each time I start to prepare something that has a thread - if I fail, is that thread started? Must I wait for it to stop? And what if threadShouldExit has been called on my thread? Must I then wait on that other thread to finish computing?
I do NOT want to wait around for 100ms in my destructor while some computation thread I started deigns to notice me!
Callbacks and async
I’m also getting great mileage out of those functions I posted about above - except that they encourage you to create callbacks and I created a callback loop, which wasn’t at all obvious because of the async calls and didn’t have any obvious side-effects except occasional long delays on the message queue (these were “null” messages that simply mean “emit your entire state”).
Thread Priorities
I wanted also to talk about thread priorities. I don’t use 'em. I never trust thread priorities after my early Java experiences. All my threads have default priority and they seem to work just fine - I try to avoid spinlocks and instead use wait/notify.
NEEDED: An atomic lock/wait construct.
I should add that that’s the one area where I feel that Juce is deficient in threads - that there isn’t a truly atomic wait/notify construct as there is in Java, and as a result there’s a race condition. (I should add that I’m mostly a C++ programmer but am using Java as an example here…)
Here’s why. In a deterministic world, there is never a need to poll anything. Instead, your thread should wait(-1) (wait forever) and another thread should notify it.
In Java, waiting involves a lock as well as a thread, so you can do this and there’s no possible race condition. In Juce, your “notify” can come in between the time you check your condition (which probably involves a lock) and the time you wait() - so you miss the notify(), and you wait forever. This is unacceptable so all your wait loops have to have a specific timeout, and a fairly small one.
What you need is some Juce construct that allows your thread to hold a lock, go into a wait state, and then have the lock released - so there is no time between letting go the lock and waiting() and therefore no race condition.
Race conditions really matter!
If I’ve learned one thing from writing a DSP application, that’s that if you have any race condition, no matter how unlikely it seems to you, if you leave your programming running overnight you will trip it. If your clients use your program an hour a day, that means every day somewhere between one in 10 and one in 100 of them will experience the results of any race condition you have missed…