High CSwitch ("context switch") when using Boost interprocess code (on Windows, Win32)

1k views Asked by At

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:

  1. Does this "high" CSwitch value matter?
  2. 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)?
  3. Would this occur if boost was using WaitForSingleObject?
  4. 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

2

There are 2 answers

0
Éric Malenfant On

On non-POSIX systems, it seems that interprocess_condition is emulated using a loop, as you describe in your in question. And interprocess_semaphore is emulated with a mutex and an interprocess_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.

1
deepsnore On

If you do not mind a Windows specific (newer versions on windows), check the link for light weight condition variables CONDITION_VARIABLE (like critical sections):