I'm writing a multithreaded app.
I was using the boost::interprocess classes (version 1.36.0)
Essentially, I have worker threads that need to be notified when work is available for them to do.
I tried both the "semaphore" and "condition" approaches.
In both cases, the CSwitch (context switch) for the worker threads seemed very high, like 600 switches per second.
I had a gander at the code and it seems like it just checks a flag (atomically using a mutex) and then yields the timeslice before trying again next time.
I was expecting the code to use WaitForSingleObject or something.
Ironically, this was exactly how I was doing it before deciding to do it "properly" and use Boost! (i.e. using a mutex to check the status of a flag regularly). The only difference was, in my approach I was sleeping like 50ms between checks so I didn't have the high CSwitch problem (and yes it's fine for me that work won't start for up to 50ms).
Several questions:
- Does this "high" CSwitch value matter?
- Would this occur if the boost library was using CRITICAL_SECTIONS instead of semaphores (I don't care about inter-process syncing - all threads are in same process)?
- Would this occur if boost was using WaitForSingleObject?
- Is there another approach in the Boost libs that uses the aforementioned Win32 wait methods (WaitForXXX) which I assume won't suffer from this CSwitch issue.
Update: Here is a pseudo code sample. I can't add the real code because it would be a bit complex. But this is pretty much what I'm doing. This just starts a thread to do a one-off asynchronous activity.
NOTE: These are just illustrations! There is loads missing from this sample, e.g. if you call injectWork() before the thread has hit the "wait" it just won't work. I just wanted to illustrate my use of boost.
The usage is something like:
int main(int argc, char** args)
{
MyWorkerThread thread;
thread.startThread();
...
thread.injectWork("hello world");
}
Here is the example using boost.
class MyWorkerThread
{
public:
/// Do work asynchronously
void injectWork(string blah)
{
this->blah = blah;
// Notify semaphore
this->semaphore->post();
}
void startThread()
{
// Start the thread (Pseudo code)
CreateThread(threadHelper, this, ...);
}
private:
static void threadHelper(void* param)
{
((MyWorkerThread*)param)->thread();
}
/// The thread method
void thread()
{
// Wait for semaphore to be invoked
semaphore->wait();
cout << blah << endl;
}
string blah;
boost::interprocess::interprocess_semaphore* semaphore;
};
And here was my "naive" polling code:
class MyWorkerThread_NaivePolling
{
public:
MyWorkerThread_NaivePolling()
{
workReady = false;
}
/// Do work asynchronously
void injectWork(string blah)
{
section.lock();
this->blah = blah;
this->workReady = true;
section.unlock();
}
void startThread()
{
// Start the thread (Pseudo code)
CreateThread(threadHelper, this, ...);
}
private:
/// Uses Win32 CriticalSection
class MyCriticalSection
{
MyCriticalSection();
void lock();
void unlock();
};
MyCriticalSection section;
static void threadHelper(void* param)
{
((MyWorkerThread*)param)->thread();
}
/// The thread method
void thread()
{
while (true)
{
bool myWorkReady = false;
string myBlah;
// See if work set
section.lock();
if (this->workReady)
{
myWorkReady = true;
myBlah = this->blah;
}
section.unlock();
if (myWorkReady)
{
cout << blah << endl;
return;
}
else
{
// No work so sleep for a while
Sleep(50);
}
}
}
string blah;
bool workReady;
};
Cheers,
John
On non-POSIX systems, it seems that
interprocess_condition
is emulated using a loop, as you describe in your in question. Andinterprocess_semaphore
is emulated with a mutex and aninterprocess_condition
, so wait()-ing ends up in the same loop.Since you mention that you don't need the interprocess synchronization, you should look at Boost.Thread, which offers a portable implementation of condition variables. Amusingly, it seems that it is implemented on Windows in the "classical" way, using a... Semaphore.