Win9x Multithreading Basics

Started by
8 comments, last by blahpers 22 years, 8 months ago
Yeah, I know, old hat, but all the other posts I read got more complicated than I needed. ''K, so I like the idea of splitting the game (or other app) away from the "message pump", as in the article. So I want multithreading. I only want the game split from the message pump. Maybe give the input its own thread, maybe handle it in the game thread, maybe in WinProc(); haven''t decided yet. That''s why I''m here. As far as I see it, the only thing I should worry about is simultaneous access of a shared data object (global, etc.) by multiple threads. That would suck. I''m not using any networking stuff right now; I''ll burn that bridge when I come to it. So. Is this where critical sections come in? I''m not too clear on how to protect my app from the above scenario. Synchronization? I''ve got a wobbly handle on the concept, and even less on critical sections in particular. Where do I put the code for it? And should I be using critical sections at all, or is it overkill for me? The only data I should be sharing between threads is input states. If it''s too much of a pain, I''ll just keep it in the game thread, but I didn''t really want to do that. --- blahpers
---blahpers
Advertisement
Shared data isn''t neccessarily global, it''s any data that any two threads can access which can be data inside of a class.

To start my game thread I do this in the initialize routine of my CGameEngine clas.
	unsigned threadID;	if(!(m_thrGame = (HANDLE) _beginthreadex(NULL, 0, GameProc, this, 0, &threadID)))		return(S_FALSE);	SetThreadPriority(m_thrGame, THREAD_PRIORITY_IDLE); 

And GameProc does this:
UINT CGameEngine::GameProc(void* pv)	{	return reinterpret_cast(pv)->Run();	}UINT CGameEngine::Run()	{	UINT uiResult = timeBeginPeriod(1);	CoInitializeEx(NULL, COINIT_MULTITHREADED)...	//initialize other game engine sub-systems...	while(WAIT_OBJECT_0!=WaitForSingleObject(m_evExit, m_iYieldTime_ms))		{		//Main Game Loop		EnterCriticalSection(&m_csRender);			Render(m_fElapsed_sec);			m_GrfEngine->Flip();		LeaveCriticalSection(&m_csRender);		}	} 



A Critical Section ensures that only one thread is executing a portion of code at a time. Think of it as a traffic light. If you had multiple threads all executing the same CGameEngine::Run()
Now, it''s important that you wrap the rendering portion of the main game loop in a enter/leave critical section because the message pump may also need to mess with the graphics (in a window''ed application it will anyway).

For instance the message pump may call the OnMove event, in which case we need to update the location of our frame buffer.
void CGameEngine::OnMove()	{	if(m_GfxEngine)		{		EnterCriticalSection(&m_csRender);		m_GfxEngine->UpdateWindowBoundaries();		m_GfxEngine->Flip();		LeaveCriticalSection(&m_csRender);		}	} 


Now, with both of these peices of code taking the same critical section, only one can execute from start to finish before the other one can. If we are in the middle of a render when the OnMove event comes, the OnMove method will wait until the render is complete, then update the boundaries and reflip.

Though it''s not absolutely critical that the window boundaries not be updated mid-render, it would result in graphic artifacts if it was called mid-flip.

It''s important to lock a window resize in the with the same critical section because it changes the secondary buffers which will definetly affect the render.


I''ve thought about creating a thead dedicated to input, and run it at a high priority so that you get consistent results from the keyboard. Right now the affect of hitting a key on the keyboard depends on the elapsed time, which depends on the frame rate. If input had it''s own (high priority) thread, the elapsed time would be a more consistent. Other than that, I can''t think of any reason to use a thread for input. So long as the frame rate is above about 20, you wouldn''t be able to tell the difference. The game thread would still need to ask the input sub-system for information once per frame.

Now, suppose you decided to do something silly, like update the position of geometry in the seperate input thread. Then you would need to lock the geometry with a critical section, and take the critical section when rendering and when updating. I don''t think the system can afford thousands of critical sections, so you would need to use one for all the geometry. Now you''ve just locked the two threads together, the input thread can do nothing while the game thread renders, and then it could uselessly update the geometry multiple times in-between renders. You would need a critical section to do that - but don''t do that.


Once you get to DirectPlay, you''ll need a critical section to hanlde network packets, because DP is multi-threaded. That means you can have multiple packets being processed "concurrently", so you need to protect the data structures from being updated from two threads at the same time.

Once you have a mutli-threaded system, you no longer have a guaranteed order of execution. So you need to ask yourself, will this code break if it is executed out-of-order. For instance, you can''t add two nodes to a linked list at the same time, because it takes mutliple steps to add a node. If you get swapped out in the middle of an add to add another node, you will lose data, and perhaps crash the machine.

Magmai Kai Holmlor
- Not For Rent
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
whats the difference between CreateThread
and _beginthreadex ???



{ Stating the obvious never helped any situation !! }
Sweet stuff. Thanks, my little demo is now thread-safe . . . for now . . . (heh)

---
blahpers
---blahpers
quote:Original post by blahpers
As far as I see it, the only thing I should worry about is simultaneous access of a shared data object (global, etc.) by multiple threads.

So. Is this where critical sections come in? I''m not too clear on how to protect my app from the above scenario.

---
blahpers


There are are two things to worry about: simultaneous access of shared data, and simultaneous access of hardware.

To prevent simultaneous access of data it''s more usual to use a mutex (mutually exclusive) object, via CreateMutex, and access this first before accessing, your data.

This works better than having critical sections as you can usually access the same data through different code paths, and having the critical sections wouldn''t prevent a thread collision.

Critical sections should be used more for absolutely time-critical code sections or places where the hardware is communicated to, such as resizing the window in Magmai Kai''s example below.


Er, so in general mutex objects should be used for shared data.
Critical sections used for code that cannot be run safely alongside other code.



Game production:
Good, quick, cheap: Choose two.
Game production:Good, quick, cheap: Choose two.
_beginthreadex() does things a little differently with the C runtime. Apparently (according to MSDN) CreateThread() has a memory leak when you use the C runtime in your threads. _beginthreadex() does not.

War Worlds - A 3D Real-Time Strategy game in development.
quote:Original post by Danack
There are are two things to worry about: simultaneous access of shared data, and simultaneous access of hardware.

To prevent simultaneous access of data it''s more usual to use a mutex (mutually exclusive) object, via CreateMutex, and access this first before accessing, your data.

This works better than having critical sections as you can usually access the same data through different code paths, and having the critical sections wouldn''t prevent a thread collision.

Critical sections should be used more for absolutely time-critical code sections or places where the hardware is communicated to, such as resizing the window in Magmai Kai''s example below.

Er, so in general mutex objects should be used for shared data.
Critical sections used for code that cannot be run safely alongside other code.
Isn''t the only difference between using mutexes and critical sections was that critical sections could only be used by threads of a single process, while mutexes could be used by multiple processes? Maybe (probably) I''m missing something. What exactly do you mean by "critical sections wouldn''t prevent a thread collision"?
---blahpers
quote:
... This works better than having critical sections as you can usually access the same data through different code paths, and having the critical sections wouldn't prevent a thread collision.

Unless I've missed something, the only difference between a critical sections and mutexes, is that (under Windows) a mutex is system wide, and critical sections are process wide. Otherwise they behave the same way... What's a thread collision?

Critical sections are entered and left a bit faster than mutexes, so unless you need to twiddle between processes, critical sections fit the bill, as I see it.



Magmai Kai Holmlor
- Not For Rent

Edited by - Magmai Kai Holmlor on August 17, 2001 2:25:39 AM
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
Heh, I think we tapped into the same collective (un-)consciousness that time.
---blahpers
Critical sections are a lot less flexible than mutexes, though they''re faster. But one important thing you can''t do with a CS is wait on it with WaitFor.. win32 functions. This is its major detraction. Mutexes may also be named and used across processes, if you''re into that kind of thing.

I''m afraid I couldn''t make heads or tails of Danack''s post. CS will stop threads from simultaneously running that part of code in the same way a Mutex will. And hardware/software has nothing to do with anything--it''s all software from your program''s point of view, even if the lines you''re accessing may be connected to hardware instead of memory.

Mutexes and critical sections basically serialize the code they protect. The code that runs between EnterCS/LeaveCS, or between WaitForSingleObject/ReleaseMutex, is assured to run in serial. This code will not be interrupted by another thread that uses the same critical section or mutex.

This topic is closed to new replies.

Advertisement