• Advertisement
Sign in to follow this  

First time using threads. Am I doing it correctly?

This topic is 2770 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi there!

I've implemented a test-thread into my game. Just to see how those things work. Now I would like to know if I have done this all correctly. Code wise. The thread seems to work properly.

Note that this is a very simple example, without any "Mutexes" and the other safety stuff I read about. This comes later [smile]




struct TestThreadWorkStruct
{
//This class calculates the Frames per second (Save to leave it public)
CTimer ThreadFPS;

//Just to make things cleaner
void StopThread()
{
bStop=true;
}

//Init variables
TestThreadWorkStruct()
{
bStop=false;
}
private:
//Should the thread stop?
bool bStop;
};

TestThreadWorkStruct TestThreadWork; //Work for the test thread


...


int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow )
{
srand((unsigned)timeGetTime());

MainHWND=CreateDialog(hInstance,L"IDD_Game",NULL,(DLGPROC)GameDlgProc);

registerMouse();

Scene.InitScene();
BlockPlacer.Scene=&Scene;





//Create a thread for testing
HANDLE TestThread;
DWORD TestThreadID;


TestThread = CreateThread( NULL,
0,
ThreadFunc,
(LPVOID)&TestThreadWork,
0,
&TestThreadID
);





MSG msg;
while(g_bWantsQuit==false)
{
bool bGotMsg;
// Use PeekMessage() so we can use idle time to render the scene.
bGotMsg = ( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) != 0 );

if( bGotMsg )
{

TranslateMessage( &msg );
DispatchMessage( &msg );

}
else
{
RenderScene();
}
}


//Tell the thread to get finieshes with it's work
TestThreadWork.StopThread();

//Wait until the test thread has finished the work
WaitForMultipleObjects( 1,
&TestThread,
TRUE,
INFINITE );

CloseHandle(TestThread);

...
}

...

DWORD WINAPI ThreadFunc(LPVOID Data)
{
TestThreadWorkStruct* Work=(TestThreadWorkStruct *)Data;
Work->ThreadFPS.Start();

while(Work->bStop==false)
{
Work->ThreadFPS.Update();
}

Work->ThreadFPS.Stop();
return 1;
}



(I changed the order of the code pieces a bit)

In my HUD-Render-Function (in the main-thread) I simply do

float TestFPS=TestThread.ThreadFPS.GetFPS();


Can I do it like this? I mean, have a struct with data which tells the thread what to do? And then receive the data from it in another thread?

Share this post


Link to post
Share on other sites
Advertisement
- Make bstop volatile.
- WaitForSingleObject, there is only one thread.

Quote:
I mean, have a struct with data which tells the thread what to do? And then receive the data from it in another thread?

As long as data is properly synchronized, then yes. For single integer value, one of InterlockedXYZ functions can be used to update the value.

Share this post


Link to post
Share on other sites
Quote:
Original post by Antheus
- Make bstop volatile.


To the OP, beware that the solution suggested by Antheus is not portable. The semantics of 'volatile' are compiler-specific. In all Visual Studio versions since 2005, volatile actually works for this type of scenario, but it doesn't in older Visual Studio versions, and there may very well be other Windows compilers (not to mention other operating systems and platforms) where it doesn't either.

In general, 'volatile' is completely unsuitable for use in multi-threading and should never be used except for its original intended purpose. The safe and portable way would be to use critical sections / mutexes to synchronize access to any data shared across threads.

Also, I recommend using boost::thread instead of using your operating system's threadig API directly. It's become pretty much a standard these days, plus its use of scoped locks (letting RAII handle the acquisition and release of mutexes implicitly) is infinitely superior to having to LeaveCriticalSection() manually (and probably forgetting to do so in a few places, which creates potential for deadlocks and other fun bugs).

Share this post


Link to post
Share on other sites
Ok I'm a bit confused right now.

Quote:

For single integer value, one of InterlockedXYZ functions can be used to update the value.

I don't get what the MSDN says: What is a Atomic operation in fact? It updates a value, but how exactly? When I call this function to update a value, what happens when another thread wants to read/write this value?


Quote:

Also, I recommend using boost::thread instead of using your operating system's threadig API directly. It's become pretty much a standard these days, plus its use of scoped locks (letting RAII handle the acquisition and release of mutexes implicitly) is infinitely superior to having to LeaveCriticalSection() manually (and probably forgetting to do so in a few places, which creates potential for deadlocks and other fun bugs).


I heard of the boost::threads. But for now I just wanted to get a simple thread working. If it's better I will switch to them.

Threading is confusing. [grin]

Share this post


Link to post
Share on other sites
Quote:
Original post by mind in a box
When I call this function to update a value, what happens when another thread wants to read/write this value?



Naturally, any thread trying to access that data must use one of the interlocked functions, too (otherwise the result is undefined). If all threads stick to that rule, the implementation of the interlocked functions makes sure there are no inconsistencies.

Share this post


Link to post
Share on other sites
Good to know.

Did I understand the Critical Section right?

Thread A:

while(1)
{
BeginCriticalSection
g_Health*=5;
g_Health=DoMoreFoo(g_Health);
LeaveCriticalSection
}


Thread B:

while(1)
{
Draw(g_Health);
g_Health=20;
}


will I be able to read from g_Health in the draw call when I am in the critical section? Or will ThreadB stop and wait until ThreadA has finished the section? Or will that happen when ThreadB writes to g_Health?

A critical section stops other threads from accessing variables in them, don't them?

If not, what technique should I use if I have such a scenario?

Share this post


Link to post
Share on other sites
Quote:
Original post by mind in a box
Good to know.

Did I understand the Critical Section right?

Thread A:

while(1)
{
BeginCriticalSection
g_Health*=5;
g_Health=DoMoreFoo(g_Health);
LeaveCriticalSection
}


Thread B:

while(1)
{
Draw(g_Health);
g_Health=20;
}


will I be able to read from g_Health in the draw call when I am in the critical section? Or will ThreadB stop and wait until ThreadA has finished the section? Or will that happen when ThreadB writes to g_Health?

A critical section stops other threads from accessing variables in them, don't them?

If not, what technique should I use if I have such a scenario?



The problem in your little snippet is that, while thread A respects the fact that access to g_Health must be synchronized by performing any accesses inside a critical section, thread B just goes and modifies it when ever anyway. :)

For synchronization to be meaningful, any and all threads wishing to access the protected data must use a critical section. And not just ANY critical section ... it has to be the exact same critical section, too.


// pseudo code

CRITICAL_SECTION cs_for_g_Health

// thread A
while(1)
{
BeginCriticalSection( cs_for_g_Health );
g_Health*=5;
g_Health=DoMoreFoo(g_Health);
LeaveCriticalSection( cs_for_g_Health );
}

//Thread B:
while(1)
{
BeginCriticalSection( cs_for_g_Health );
Draw(g_Health);
g_Health=20;
LeaveCriticalSection( cs_for_g_Health );
}



Share this post


Link to post
Share on other sites
Quote:
Original post by mind in a box
A critical section stops other threads from accessing variables in them, don't them?


Yeah, a critical section can only be 'owned' or 'held' by one thread at any point in time (after all, that's the whole point!). While one thread has acquired the critical section, any other threads also wishing to acquire it must wait. The wait ends the moment the thread currently owning the critical section releases it, at which point one of the threads currently waiting for the critical section will acquire it.

Share this post


Link to post
Share on other sites
Quote:
Original post by mind in a box
So, everytime (When it makes sense) I have to change a important variable or I have to compute something, I have to surround them with CriticalSections?


If more than one thread could potentially access that data __and__ at least one of those threads actually MODIFIES the data (as opposed to just reading it), yes. If you have n readers and at least one writer, you need to lock. If you only have n readers, no locking is necessary.

You've probably guessed by now that this doesn't come for free. Frequent mutex locking costs performance and in extreme cases it may even totally kill any parallelism in your program, since while threads are waiting to acquire some mutex, they can't do any work. That's why, in a real world program, you want to avoid shared data as much as possible. Obviously, your threads WILL usually have to share some data, but you should keep it down to a minimum.

For instance, rather than having one shared instance of a struct and constantly locking mutexes to access that instance each time a thread needs to read or write a member of that struct, it may be better give each thread its own copy of that struct, then use that without any locking and then lock a mutex just once per game cycle to synchronize both copies of the struct with each other.

Share this post


Link to post
Share on other sites
One final piece of advice: if you're not going to use boost::threads right away, at least make sure you use one of its best features, which can be easily implemented even with the standard WIN32 threading API (I'll show you how in a second). I'm talking about those scoped locks I already mentioned before.





/// Rather than sprinkling explicit calls to EnterCriticalSection() and LeaveCriticalSection() all over your code,
/// use this class to handle it for you. Simply create an instance of this class on the stack every time you need
/// to work with a critical section. The constructor will acquire the critical section for you. Once the
/// ScopedLock instance goes out of scope, the destructor automatically takes care of releasing
/// the critical section again.
/// See the example below.
class ScopedLock
{
public:
/// The constructor acquires the critical section and stores a pointer to it so that we can later LeaveCriticalSection()
/// in the destructor.
explicit ScopedLock( CRITICAL_SECTION& criticalSectionToAcquire ) : m_criticalSection( criticalSectionToAcquire ), m_acquired( true )
{
::EnterCriticalSection( &criticalSectionToAcquire );
}

/// The destructor automatically releases the critical section.
~ScopedLock()
{
release();
}

/// Function to explicitly release the critical section before this instance is destroyed, should you ever need to.
void release()
{
if ( ! m_acquired )
{
return;
}

::LeaveCriticalSection( &m_criticalSection );
m_acquired = false;
}

private:
CRITICAL_SECTION& m_criticalSection;
bool m_acquired;

// prevent copying and assigning
explicit ScopedLock( const ScopedLock& );
ScopedLock& operator = ( const ScopedLock& );
};

// usage example
CRITICAL_SECTION myCriticalSection;

bool globalBool = true;


void threadFunction_A()
{
const ScopedLock sl( myCriticalSection );

if ( ::globalBool )
{
// ...
}
}

void threadFunction_B()
{
const ScopedLock sl( myCriticalSection );

::globalBool = false;
}




Share this post


Link to post
Share on other sites
Read this.


example:


boost::mutex theMutex;
bool globalBool = false;

void threadFunction_A()
{
const boost::mutex::scoped_lock sl( theMutex );
globalBool = true;
}

Share this post


Link to post
Share on other sites
Hey no problem, us sauerkraut and bratwurst munchers have to stick together, eh? ;)

Share this post


Link to post
Share on other sites
How do I build that damn "libboost_thread-vc100-mt-gd-1_42.lib"?
Sorry, I'm a bit annoyed right now. I've been messing around with the boost docs for almost an hour:

http://www.boost.org/doc/libs/1_36_0/more/getting_started/windows.html#prepare-to-use-a-boost-library-binary (Yes...)
http://www.boost.org/doc/libs/1_36_0/tools/build/index.html (hmpf)
http://www.boostpro.com/products/free (404?)
http://sourceforge.net/project/showfiles.php?group_id=7586&package_id=8041 (What??)

I downloaded that bjam tool and it wants boost.build which takes a couple of minutes to download here. ... Oh, and right now the download stopped [smile]

A great start [sad]

EDIT: Weee! Download crashed! Again...

EDIT2: And it looks like that this from the documentation isn't working:

C:\WINDOWS> cd C:\Program Files\boost\boost_1_36_0
C:\Program Files\boost\boost_1_36_0> bjam ^
More? --build-dir="C:\Documents and Settings\dave\build-boost" ^
More? --toolset=msvc stage


Produces:

C:\WTech DX10\boost_1_42>bjam ^
Mehr? --build-dir="c:\WTech DX10\boost_1_42\build" ^
Mehr? --toolset=msvc stage
notice: could not find main target stage
notice: assuming it's a name of file to create
C:/WTech DX10/boost_1_42/build\project.jam:699: in attribute
warning: rulename $($(project).attributes).get expands to empty string
C:/WTech DX10/boost_1_42/build\project.jam:709: in project.target
C:/WTech DX10/boost_1_42\build-system.jam:395: in load
C:\WTech DX10\boost_1_42\kernel\modules.jam:261: in import
C:\WTech DX10\boost_1_42\kernel\bootstrap.jam:132: in boost-build
C:\WTech DX10\boost_1_42\boost-build.jam:1: in module scope
C:/WTech DX10/boost_1_42/build\project.jam:699: in project.attribute
warning: rulename $($(project).attributes).get expands to empty string
C:/WTech DX10/boost_1_42/build\targets.jam:203: in object(project-target)@34.__i
nit__
C:/WTech DX10/boost_1_42/kernel\class.jam:93: in new
C:/WTech DX10/boost_1_42/build\project.jam:709: in project.target
C:/WTech DX10/boost_1_42\build-system.jam:395: in load
C:\WTech DX10\boost_1_42\kernel\modules.jam:261: in import
C:\WTech DX10\boost_1_42\kernel\bootstrap.jam:132: in boost-build
C:\WTech DX10\boost_1_42\boost-build.jam:1: in module scope
C:/WTech DX10/boost_1_42/build\project.jam:699: in project.attribute
warning: rulename $($(project).attributes).get expands to empty string
C:/WTech DX10/boost_1_42/build\targets.jam:222: in get
C:/WTech DX10/boost_1_42/build\targets.jam:284: in targets-to-build
C:/WTech DX10/boost_1_42/build\targets.jam:253: in object(project-target)@34.gen
erate
C:/WTech DX10/boost_1_42\build-system.jam:414: in load
C:\WTech DX10\boost_1_42\kernel\modules.jam:261: in import
C:\WTech DX10\boost_1_42\kernel\bootstrap.jam:132: in boost-build
C:\WTech DX10\boost_1_42\boost-build.jam:1: in module scope
C:/WTech DX10/boost_1_42/build\project.jam:699: in project.attribute
warning: rulename $($(project).attributes).get expands to empty string
C:/WTech DX10/boost_1_42/build\targets.jam:222: in get
C:/WTech DX10/boost_1_42/build\targets.jam:285: in targets-to-build
C:/WTech DX10/boost_1_42/build\targets.jam:253: in object(project-target)@34.gen
erate
C:/WTech DX10/boost_1_42\build-system.jam:414: in load
C:\WTech DX10\boost_1_42\kernel\modules.jam:261: in import
C:\WTech DX10\boost_1_42\kernel\bootstrap.jam:132: in boost-build
C:\WTech DX10\boost_1_42\boost-build.jam:1: in module scope
don't know how to make <e>stage
...found 1 target...
...can't find 1 target...




[sad]

Share this post


Link to post
Share on other sites
Assuming you have boost installed to C:\boost\boost_1_43_0, do the following things on the command line ...

- cd into "C:\boost\boost_1_43_0"
- bjam --build-dir=. --toolset=msvc --build-type=complete stage


As an aside, if you have multiple Visual Studio versions on your machine, bjam will normally build boost for the latest version. I.e. if you have both Visual C++ 2008 and Visual C++ 2010, it will only build for Visual C++ 2010. To force it to build for Visual C++ 2008:

- bjam --build-dir=. --toolset=msvc-9.0 --build-type=complete stage


And yeah, I really wish they'd update their 'Getting started' documentation ... I've had trouble with their instructions, too, until I found a working command line somewhere on the internet.

Share this post


Link to post
Share on other sites
This complains about the same thing as above.
I still use 1_42, is that a problem? I don't want to download that whole thing again [sad] (56k Mobile Webstick <- Takes almost days)

Share this post


Link to post
Share on other sites
Hmmm, no 1_42 should be fine. I have both 1_42 and 1_43 on my computer and both builds worked fine. The problem must be something else.

When you step into "C:\WTech DX10\boost_1_42", does that directory contain the following?

- directory "boost"
- directory "doc"
- directory "libs"
- directory "more"
- directory "people"
- directory "status"
- directory "tools"
- directory "wiki"
- file "boost-build.jam"
- file "bootstrap.bat"
- file "Jamroot"

And a couple of other files?

Share this post


Link to post
Share on other sites
no, just a "boost" directory and nothing else (Except the files of Boost.Build which I copied in there a few minutes ago)

The documentation said that I only need this one folder. I will look if I still have the other ones (I really think I've seem them somewhere)

EDIT: Oh no! I formated my HDD a couple moths ago. I guess I have to re-download them. [sad]

Share this post


Link to post
Share on other sites
Normally you just unzip boost to some directory, then step into the directory that contains the files and folders mentioned and then invoke bjam with the specified parameters. You shouldn't have to delete or move anything around.

Share this post


Link to post
Share on other sites
The procedure for building Boost under Windows is quite straightforward. Download and unzip the source archive. In the root folder, named boost_1_43_0 or similar, run the "bootstrap" batch file. That should build bjam. With that done, run bjam.exe from the same directory (it should have appeared when bootstrap finished). With that done, you should have a bin.v2 directory. Go deep into that tree, and you'll find your library.

Share this post


Link to post
Share on other sites
I got the library to build now, thanks to Windryder for his explanation!

But I have a heavy FPS decrease when my worker thread does work. How can I put it to my second CPU-core? It would be the only thing running there...

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement