"Nothing will ever be attempted if all possible objections must first be overcome." - Samuel Johnson   |    "Take calculated risks. That is quite different from being rash." - George S. Patton   |    "We are still masters of our fate. We are still captains of our souls." - Winston Churchill   |    "Nothing great was ever achieved without enthusiasm." - Ralph Waldo Emerson   |    "The talent of success is nothing more than doing what you can do, well." - Henry W. Longfellow   |    "The big secret in life is that there is no big secret. Whatever your goal, you can get there if you're willing to work." - Oprah Winfrey   |    "Good luck' follows careful preparation; 'bad luck' comes from sloppiness." - Robert Heinlein   |    "The more difficulties one has to encounter, within and without, the more significant and the higher in inspiration his life will be" - Horace Bushnell   |    "Perl - The only language that looks the same before and after RSA encryption." - Keith Bostic   |    "If Java had true garbage collection, most programs would delete themselves upon execution." - Robert Sewell   |    "Every artist was first an amateur." - Ralph Waldo Emerson   |    "There is no such thing as luck; there is only adequate or inadequate preparation to cope with a statistical universe." - Robert Heinlein   |    "The more you learn, the more you need to learn." - Robert Heinlein   |    Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.   |    "We are what we repeatedly do. Excellence, therefore, is not an act but a habit." - Aristotle   |    "Success is the sum of small efforts, repeated day in and day out." - Robert Collier   |    "Our doubts are traitors, and make us lose the good we oft might win, by fearing to attempt." - William Shakespeare   |    "Nothing can stop the man with the right mental attitude from achieving his goal; nothing on earth can help the man with the wrong mental attitude." - Thomas Jefferson   |   

Code Reactor

The best of modern Web development

Implementing win32-events with Boost

I’ve been trying to modify the Hydrax sources for a while. Hydrax is a great project that renders quite pretty water, but has some performance problems, the main of which being the fact that it renders the heightmap of the water using Perlin noise on the CPU, and does so synchronously. Ogre, which is the 3D-engine that Hydrax runs on, is already pretty single-threaded, which works out relatively well, but not when Hydrax adds the noise generation to the already bloated singlethreaded CPU-part of the Ogre render cycle.

So the basic idea was to take the heightmap-generation and put into another thread which will execute on another core while the main thread does the rest, including waiting for the GPU to render things. (Yep, that’s just how single-threaded a stock Ogre application is.)

Hydrax stores the height-map in a handy blob of vertex positions (also colloquially known as the vertex buffer), which is then just fed into the mesh class in it’s entirety every frame. This gave me a nice practical possibility to just have a secondary vertex buffer which would be updated in the background, so that the primary one will be free for usage by the mesh in the main thread (I didn’t have any control over how it’s used, nor wanted to dig into the details of that), and then the only place I would need to synchronize the threads would be the point of switching the primary vertex buffer to the secondary buffer which has been generated/updated in the background.

And finally we come the the actual point of this article (it wasn’t supposed to be just about Hydrax): the locking required for this kind of thing. Interestingly, a simple mutex solution does not seem to be fit for this straight up.

The main thread must wait for the secondary thread to finish the job. Not only that, but it must even know for sure that the secondary thread has in fact started doing the job and finished it, as opposed to haven’t even started, which could easily become a problem if a simple mutex was used. (Well, in practice I am not sure if this was so possible, since the secondary thread would need to not even have started while the ogre has rendered a complete frame with everything in it…)

The secondary thread (the one which is going to calculate the heightmap) must wait for the main thread to give it enough data, then do all the work, and then stop and wait again for more data.

A perfect locking solution for this, which I directly thought about from my WINAPI past are the windows events. (CreateEvent, etc…). But I didn’t like that fact that they aren’t crossplatform. And well, not only that, but neither boost, which is a heap of good practices and standard-close language conventions, nor the new C++x11 seemed to have a direct equivalent of winapi-events, which would only mean that there must be some better alternatives which are more widely used and accepted.

Naturally, there was, and the solution involved using one boost::mutex and one boost::condition, and a couple of shared variables to facilitate the locking.

boost::condition

Now, the mutex object is pretty straight-forward, it’s an object that you can lock from different threads, which ensures that only one thread will hold the lock at one time, and provides functionality for blocking thread execution until the mutex is free from locks by all other threads. But dafuq what is a “condition”?

Don’t think too much about the name, just know this: the most basic explanation is that condition is an object that can block one or several threads until another thread signals the condition, while unblocking the threads that were waiting. In a sense, a functionality very similar to windows events. So while a mutex can only be locked and waited for, a condition can also be “signaled”.

Naturally, since this is an “extra” functionality for a basic mutex, a condition is always used in conjunction with an underlying mutex and a lock. So in a sense of variables you will need:

...
#include 
...
 
boost::condition mCondition;
boost::mutex mMutex;

When you are normally using mutexes in different threads, you basically use same instruction in all of them: to block the execution until no other thread has the lock, acquire the lock and continue execution. (There are slightly other scenarios of course, but for the sake of understanding that’s the only way to use plain mutexes right now.)

When using conditions, you instead generally do two different things to them in different threads: some threads wait for the condition to be signaled, while other threads signal a condition.

Thread(s) that are waiting
A condition always uses a mutex and a lock, and the thread that wishes to wait for a condition, must first acquire a lock on a mutex. This lock is then passed to the condition wait function, which internally releases that lock. So the wait function does not wait on the lock itself to be released (since it releases it directly anyway), but it is waiting on the signaling of the condition. When the condition is signaled, the wait function also tries to acquire the lock again, and returns only when that lock can be aquired.

So for the waiting thread it “looks” like it has been holding the lock all the time, while in reality the lock has been released and acquired again inside the condition wait function:

void someThread() {
    boost::lock_guard<boost::mutex> lock(mMutex); // Must acquire lock before waiting for a condition.
    while (true) {
        doSomething();
        mCondition.wait(lock); // The lock is released and acquired again inside wait(), returns when the condition is signaled and lock is available again.
    }

This design makes it possible for multiple threads to wait on the same condition, and at the same time share some resource which still needs to be protected from shared access by a mutex.

The mutex that is used in the condition can be used for other things and locked in the same way any other mutex would. The condition acts simply as an add-on functionality.

For example in my Hydrax case I would only have 1 thread that would wait on a condition, but the use of a lock fixed the rest of my synchronization needs: the main thread could lock the same mutex as the secondary thread does, which would ensure that the main thread code would continue only after the secondary thread would have completed it’s work.

Thread(s) that are signaling
For the thread that is doing the signaling it is much simpler, it doesn’t even need to hold any locks, it’s just a matter of

mCondition.notify_all(); // there is also a .notify_one() which only signals one of the waiting threads.

In my case the signaling thread also tried to acquire the same lock to make sure that the secondary thread has finished it’s work before signaling it again.

The function notify_all() will notify all the waiting threads (if there are more than 1), but of course, keep in mind that waiting for a condition requires acquiring a lock, so if multiple threads wait for a condition using the same lock as the argument, only one of them will wake up at a time, simply because the lock can only be acquired by a single thread at a time.

Important complication: spurious wakeups

There is one important detail to consider when doing conditions: for some technical reasons a thread that is waiting for a condition can be woken up without an actual call to notify_all() function. For one, if there were multiple threads waiting for the same condition/lock combination, once the first of them will release the lock (perhaps by waiting for the condition again), the second one will be woken up, even though the notify_all() call may have been done some time ago.

But more importantly, due to implementation details, a thread can sometimes be woken up (condition.wait() returns control) even if notify_all was not called at all. That is why the standard way of doing a condition is (instead of the example in the previous code block) goes something like this:

bool mSignalSent=false;
...
// Waiting thread:
// Instead of just mCondition.wait():
 
boost::lock_guard<boost::mutex> lock(mMutex);
while (!mSignalSent) {
    mCondition.wait(lock);
}
doSomething();
 
// And in the signaling thread:
boost::lock_guard<boost::mutex> lock(mMutex);
mSignalSent = true;
mCondition.notify_all();

The variable mSignalSent acts as an extra information piece needed for the waiting thread to know that it actually has been signaled. Based on your implementation you might not need a dedicated variable, for example if your threads are waiting for a buffer to be filled, they could just check if the buffer is filled, etc. The important thing to rememeber is that whether the buffer check or a variable check, it needs to be protected by some kind of lock in both threads. In the example the same lock is used for this as the one for the condition itself, but you might need another lock also, if you don’t want to necessary block the signaling thread during the notify_all() function. (Well, it will have to be blocked some time anyway, because you have to acquire some lock for the mSignalSent funtion, but if you have a mutex that is dedicated just to access to that variable, the lock times will be minimal. In the example above the main thread will be blocked until the secondary thread does not release the only lock used, which may take long time in your application.)

That’s about everything you need to get started on conditions, blessed be Boost by the gods of C++.

Leave a Reply

You must be logged in to post a comment.

loading...
Your connection appears to be too slow, automatically disabling HeavyAjax (TM) for better performance...
You seem to run a browser without JavaScript support or it has been disabled. To fully experience Code Reactor please enable JavaScript. (It is not 1995 anymore :)
You seem to be using Internet Explorer. If you want to experience both Code Reactor and the rest of the web to their fullest and fastest, you are advised to download and install a real browser, like Opera, Firefox or Google Chrome.
Close
You seem not only to use Internet Explorer, which is by far a joke when it comes to browsers, but to even use an old version of it!
If you want to experience both Code Reactor and the rest of the web to their fullest and fastest, you are STRONGLY advised to download and install a real browser, like Opera, Firefox or Google Chrome.
Close