Click here to Skip to main content
15,881,852 members
Please Sign up or sign in to vote.
4.00/5 (2 votes)
See more:
Hi there,

My current project involves having several worker threads running a simulation loop as fast as possible, with the main thread displaying the current state once per frame. The frequency of the worker threads is much higher than the main thread's render.

I'm currently using Windows threads, but I've tried the equivalent in pThread, and I get the same results.

My plan is to have the worker threads at a lower priority than the main thread. Each sim loop they lock a mutex, do the sim, unlock the mutex and sleep(0).

When the higher priority main thread wants to render one of the worker threads, it locks the mutex, does the render then unlocks the mutex.

What I *think* should happen is that as soon as the main thread wants to render, it will wait for the mutex. As soon as one of the worker threads finishes the sim, it unlocks the mutex and sleeps, which should yield to any higher priority thread. So, the render should be nice and responsive.

Here's some code snippets to help clarify:

Thread creation:

mSimMutex = CreateMutex(NULL, false, NULL);
mThread = CreateThread(
    NULL,                   // default security attributes
    0,                      // use default stack size
    ThreadProc,             // thread function name
    this,                   // argument to thread function
    CREATE_SUSPENDED,       // use default creation flags
    NULL);                  // returns the thread identifie
SetThreadPriority(mThread, THREAD_PRIORITY_BELOW_NORMAL);
ResumeThread(mThread);


Thread loop:

DWORD WINAPI SimThread::ThreadProc(void* lpThis)
{
  float lfElapsed = 0.0f;
  float lfTimeStep = 1.0f/60.0f;
  SimThread* lpSim = (SimThread*)lpThis;

  while (lfElapsed < 100.0f)
  {
    WaitForSingleObject(lpSim->mSimMutex, INFINITE);
    lpSim->Step(lfTimeStep);
    lfElapsed += lfTimeStep;
    ReleaseMutex(lpSim->mSimMutex);

    Sleep(0);
  }

  return 0;
}


Main thread render function:
void SimThread::Draw()
{
  WaitForSingleObject(mSimMutex, INFINITE);
  //Snip - Drawing code
  ReleaseMutex(mSimMutex);
}


What actually happens is that the draw method blocks for a large amount of time (until the worker thread actually finishes). It looks like the main thread never takes advantage of the Sleep() on the worker thread - the worker thread just carries straight on and re-grabs the mutex.

I'm not massively experienced with threading, so I'm at a bit of a loss as to what's going on. If anyone can suggest anything I'd be hugely grateful.

Thanks,
Rob.

P.S. In case it's relevant, I'm coding in Eclipse, using MinGW/GCC toolchain, under Windows 7.
Posted

Hi Rob,

drawing of a window is either triggered by any window message (WM_PAINT) so that you have to call AssociatedThread->Draw() in the windows message procedure, or you might have a loop anywhere in your basis thread that calls aSimThreads[0..n-1]->Draw() repeately.
The last solution is bad, because threats 2...n may be ready to draw, while you are waiting INFINITE to thread 1 (until he is ready) and so on for every thread.

For serializing the drawing you don't need a mutex. Simply use SendMessage() after calculation instead of sleep() to send a user message (WM_USER + x) with the tread pointer (lpThis) in WPARAM or LPARAM to your basis thread (to your main window or to the thread associated window) and in the message handler call lpThread->Draw(). The thread will be stopped in this way until drawing is finished and SendMessage() has returned.
Have a look at the pThreads class of your development environment. May be there is a function like 'Synchronize()', doing the same.

You also can use PostMessage() that will return instantly only placing the message in basis threads message queue and allowing calculation and drawing for the same thread parallel, but you have to assure that Draw() uses a consistent copy of the calculation data sent within LPARAM/WPARAM.

An other solution is to keep on using mutexes, but calling SuspendThread() instead of Sleep(0) and ResumeThread() after drawing and changing the Draw function in this way:
C#
bool SimThread::Draw()
{
  if (WAIT_OBJECT_0 != WaitForSingleObject(mSimMutex, 5)) return false;
  //..
  ReleaseMutex(mSimMutex);
  ResumeThread();
  return true;
}


Regards
Ralf
 
Share this answer
 
Comments
RobD6 30-Dec-10 13:46pm    
Hi Ralf,

Thanks for the response. There's definitely some stuff you've mentioned that I need to look into.

I think I didn't quite explain something properly though. The frequency of the update loop is very much faster than what I'd expect the render to go at. It's probably doing something like 1000 iterations a second. I don't want to render every iteration - the point of the mutex was to allow the much lower frequency main thread (containing the render) to break in and render when it wants to, but otherwise (most of the time) the sim threads will skip straight over the sleep and move on to the next iteration, since the draw thread isn't waiting.

If I understand your suggestions properly, I think they'd lock the render and sim loops together, which would massively affect the performance of the simulation. What I'd like to happen is for the simulation to go as fast as it possibly can, with the render happening at a regular frequency (say, 60Hz). What I should see on screen is a fast-forwarded view of the simulation.

Your answer has prompted me to look into how/when the draw gets called though - I think that may be part of the problem. I was assuming it gets called at a regular frequency, but it seems that might not be the case.

Thanks,
Rob.
Look as you have a concurrency problem !

You have to check for the return value of WaitForSingleObject, it should be not what you expect, you may have problem of synchronization with lpSim->mSimMutex creation,

Detail, it is "cleaner" to put SetThreadPriority(mThread, THREAD_PRIORITY_BELOW_NORMAL); as the first instruction of the Thread proc

Have a look at this article[^]
 
Share this answer
 
Just figured it out. I'm an idiot.

It was nothing to do with the mutexes or the relative frequencies. It was a stupid typo in some housekeeping code, which checked for when worker threads marked themselves as finished and joined them. The code which worker threads used to mark themselves as finished had a really dumb bug, which meant that the main thread always thought they were finished, so was sitting there on the join.

With that fixed, my original version seems to work just fine.

Thanks for all the suggestions anyway.

Rob
 
Share this answer
 
Try using BOOST threads. It's easier to use if you are not utilizing MFC and CLR.
 
Share this answer
 
Comments
RobD6 30-Dec-10 13:56pm    
Hi Ted,

Thanks for the suggestion. I did a quick port to boost threads, but to be honest I'm not keen. It's much, much slower to build, results in a ton of compiler warnings, and while the interface is simpler and more portable, I'm likely to need some of the extra power of the native implementation that winbase supplies.
T2102 30-Dec-10 20:12pm    
It should not be a much slower build if you are dynamically linking to a dll. I do it myself with Visual C++.
In the interest of a fast going simulation and since you are drawing only snap shots of your calculation, I would try to avoid interrrupting the calculation for drawing (multi processor system presupposed).
I would use a timer in main thread (10-20ms) that sets an event E of each sim thread to inform the thread function that drawing data is needed.
- The thread function is finishing the current iteration (or may be interrrupted if E is set)
- if (WaitForSingleObject(E, 0) == WAIT_TIMEOUT) next iteration
- create a copy of simulation data to threads own drawing buffer
- PostMessage(hMainWin, WM_USER+x, (DWORD)this, 0)
- next iteration
Now the posted messages from every thread appear in main threats message handler in different order:
switch(msg) {
case WM_USER+x : ((simThread*)WPARAM)->Draw();
break;
...
}

An additional advantage of this proceeding is, that you can call the draw function of any sim thread again (after first call) without setting the event, to refresh the window after moving, resizing ...
For processor load balancing see also SetThreadAffinityMask() and SetProcessAffinityMask().

Ralf
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900