Archived

This topic is now archived and is closed to further replies.

fuzzy

multithreading

Recommended Posts

I recently read an article about removing the game from the message pump and giving it it''s own thread in the programming articles. This works great, increasing the frame rate considerably among other benefits, but I''m not very experienced with using and especially debugging multithreaded programs. What I would like is to know where to learn good multithreaded techniques. If you know of any tutorial, source code, or book on the subject, it would be a great help.

Share this post


Link to post
Share on other sites
I haven''t found a decent source on Win32 Multithreading.
I can tell you to avoid mutlithreading & MFC - MFC is not threadsafe.

If you understood the article, go read about CriticalSections & Events in the MSDN.microsoft.com - thier in the platform SDK reference section. Look up syncronization functions.

I don''t think you''ll need mutexes or semaphores writing a game - but events are extremely useful (NO MORE POLLING!!!) and critical sections are a must for a truely multithreaded application.

If you read anything about this so-called ''apartment'' thread model, disregard it. Creating an ''apartment'' threaded applications provides much less benefit than a fully multi-threaded design.

Here''s the skeleton of a CComPort class I''m working on that uses a thread & events. It works well under W2k & NT4 - I''m told that the comm support on 95 is crap, slightly better on 98, & almost works on ME.
  
class CComPort
{
public:
CComPort();
CComPort(CComPortObserver*);
~CComPort();

//Commands

public:
HRESULT Open(LPCTSTR szComPort, DWORD dwBaud, BYTE byParity, BYTE byStop);

//Worker Thread

private:
static unsigned __stdcall CommProc(void* pComPort);
HANDLE m_thrCommProc;
HANDLE m_evExit;
}

HRESULT CComPort::Open(LPCTSTR szComPort, DWORD dwBaud, BYTE byParity, BYTE byStop)
{
//init code...

//starts a thread!

if(!(m_thrCommProc = (HANDLE) _beginthreadex(NULL, 0, CComPort::CommProc, this, 0, &threadID)))
}

unsigned __stdcall CComPort::CommProc(void* This)
{
CComPort* pComPort = (CComPort*) This;
//...does stuff here...

//Events to wait on

HANDLE WaitEvent[4];
WaitEvent[0] = pComPort->m_evExit;
WaitEvent[1] = pComPort->m_OStatus.hEvent;
WaitEvent[2] = pComPort->m_ORead.hEvent;
WaitEvent[3] = pComPort->m_OWrite.hEvent;

//Process until the m_evExit event is signaled

//Just check to see if the event is signaled, don''t wait on it here

while(WAIT_OBJECT_0!=WaitForSingleObject(pComPort->m_evExit, 0))

//As soon as any event signals, we process it

//This is truely an event driven loop (there is no polling)

//As such there is no performance hit to run this thread at Time Critical

//Consequentially, this provides the lowest latency possible on Win32

switch(WaitForMultipleObjects(4, WaitEvent, FALSE, INFINITE))
_endthreadex(0);
}

Share this post


Link to post
Share on other sites
quote:
Original post by Magmai Kai Holmlor
I can tell you to avoid mutlithreading & MFC - MFC is not threadsafe.


Really? Wow, I''d better get back all of my released products from my customers and tell them my entire software paradigm is incorrect. I''ve got a possible total of 8 threads running in an MFC program. It must be by pure virtue of magic that it works at all.

Seriously, don''t post disinformation like this unless you can back it up (which you can''t, because it''s not true). MFC works fine and dandy with a single thread and with multiple threads.

Also, watch out when using critical sections: they''re faster than mutexes, but you can''t set a timeout period for waiting on them. You also can''t use them with WaitForSingleObject, which makes them different than all of the other win32 synchronization objects (thread handles, events, mutexes, semaphores, etc.). Instead, you have to use Enter and LeaveCriticalSection. This means that if you want to WaitForMultipleObjects like in the above example, you can''t have a critical section be one of the objects on which you wait.

As far as answering the original question on techniques for using and debugging, I don''t have any sources. Most is learned through extremely painful trial & error. Debugging problems that come from improperly synchronized threads is one of the most difficult tasks I''ve ever had. Some basic tips, though:

- make sure you list exactly what each mutex/critical section protects, and you adhere to that definition
- callbacks to log functions in worker threads help a lot in debugging
- if you''re using MSVC++, put the "threads" control from the debug menu on your debugging menu bar--you will learn to love it. With this control you can stop all threads but one and step through it.
- if you get into a problem where debug code works but release code doesn''t (or vice versa), odds are you have a thread synchronization problem.

That''s all the general tips I can think of off the top of my head. If you have some specific questions, I can probably answer them.

Share this post


Link to post
Share on other sites
MFC as is, is not mutli-thread safe, and it is stated as so in the MSDN docs. That of course, does not mean it cannot be used in a mutli-threaded application. MFC is 'apartment' thread safe - which is not mutli-thread safe at all.

The STL is not multi-thread safe either, but many people use it with mutli-threaded applications.

What do you think multi-thread safe means? When I say mutli-thread safe, I mean you can use the object in mutliple threads concurrently. Win32 is mutli-thread safe, but MFC is not because it utilizes thread local storage. Some MFC objects are mutli-thread safe, but not all most notibly CWnd.

So do tell, you have a mutli-threaded application, and you never call GetSafeHwnd?

From the horse's mouth:
quote:

As a general rule, a thread can access only MFC objects that it created. This is because temporary and permanent Windows handle maps are kept in thread local storage to ensure protection from simultaneous access from multiple threads. For example, a worker thread cannot perform a calculation and then call a document’s UpdateAllViews member function to have the windows that contain views on the new data modified. This will have no effect at all, because the map from CWnd objects to HWNDs is local to the primary thread. This means that one thread may have a mapping from a Windows handle to a C++ object, but another thread may map that same handle to a different C++ object. Changes made in one thread would not be reflected in the other.


Magmai Kai Holmlor
- The disgruntled & disillusioned


Edited by - Magmai Kai Holmlor on February 2, 2001 9:43:36 PM

Share this post


Link to post
Share on other sites
fuzzy: I advise studying multithreading, or it''ll cause more headaches than it''s worth. The basic idea behind synchronization in an OS like Windows is that the processor caches variables in its registers, the system manipulates memory addresses, etc. behind your back, so the basic idea is that you must not access the same data in two threads without synchronization.

Synchronization takes time. For multithreading to be efficient, you need a minimum of communication between the two threads. Clearly define what data must be accessed by what threads, and synchronize it.

If you don''t understand what you are doing, you can quite easily lock up your app, but that isn''t much different from braving DirectX exclusive mode anyway.

Share this post


Link to post
Share on other sites
quote:
Original post by Magmai Kai Holmlor

MFC as is, is not mutli-thread safe, and it is stated as so in the MSDN docs. That of course, does not mean it cannot be used in a mutli-threaded application. MFC is ''apartment'' thread safe - which is not mutli-thread safe at all.



Then why did you say, "don''t use multi-threading with MFC"? That was the statement with which I take exception.

quote:

So do tell, you have a mutli-threaded application, and you never call GetSafeHwnd?



No, I don''t. I keep UI threads and worker threads separate. The only thread that accesses each CWnd is the thread which created it. The behavior that prohibits me from doing otherwise was designed into MFC to keep calls to CWnds from colliding with each other.

You really need to think about what "thread-safe" means. Most software modules are not thread-safe; they require external synchronization. If a module has internal synchronization, it means that it has to have kernel-level synchronization objects within it. In most cases, that would be overkill. How useful would you find a string class or a linked list class that carried around a mutex that needed to be locked for every operation?

In your first post you equated "thread-safe" with "usable in multithreaded applications", and "not thread-safe" with "don''t use in multithreaded applications". This is just plain wrong.

Share this post


Link to post
Share on other sites
Calm down, Stoffel... I see your point but you won''t get anywhere by coming in guns a''blazing.

MFC can be used in multithreading, but you should be aware of how multithreading and synchronization works. Inline MFC member functions that merely pass handles and local objects (i.e., pass-by-value) are generally fine because Windows synchronizes its internal code that works with handles anyway - no need to do it twice. MFC member functions that manipulate other types of class data are generally unsafe.

There should be an article in MSDN about MFC and multithreading.

Share this post


Link to post
Share on other sites
I believe I used the word 'avoid' not 'impossible'.
And true, nearly all code is not thread safe, but most of it requires simple sycronization - MFC objects require construction inside the thread that they are used in, which a-mon-avis is not an intuitive step to take.
They are new to thread trashing, so as I see it, it would cause undue stress to deal with the additional threading issues of MFC. (I'll give you that it's not /that/ hard, but it seemed reasonable to me to pass CWnd* between threads when I first tried...)

If you wish to take issue with my opinion on apartment threads, then fine you're entitled to honor them as much I disgrace them. Neither opinion of which changes the thread unsafety of MFC

Frankly, of course apartment threading works, if you don't use the same functions, and don't pass any data between them - how could it not!?

quote:

How useful would you find a string class or a linked list class that carried around a mutex that needed to be locked for every operation?


Very useful if I was sharing the string between processes, otherwise I'd wonder why they didn't use a critical section.

The MFC functions are not reentrant, it's a much deeper problem than data access sycronization. If they didn't use TLS, and require you to construct additional objects for use in other threads, you would only be able to use a given MFC class in one thread - fortunetly they hacked a TLS solution so you can. I think they did it for performance reasons in the days of yore that just don't apply any more... It doesn't really matter if it takes 60us instead of 6us to resolve one msg, at the dawn of Win95, it would have been 1200us instead of 120us.

Hey null,
quote:

MFC member functions that manipulate other types of class data are generally unsafe.


But it's worse than that. If you pass a CWnd* from one thread to another, the second thread will not have access to the msg map for the WndProc... I'm not entirely certain what all the issues are, and they are notably evasive in the MSDN docs.
All I know is that I got a ton of assertion failures & crashes Can can work around it; you have to construct your CWnd in each thread you want to use it in from the native HWND. But then you're mutli-threading with Win32 handles not MFC objects.

If the only threading issue with MFC was a simple EnterCriticalSection(csMFC), it wouldn't be a big deal...

Edited by - Magmai Kai Holmlor on February 3, 2001 7:13:11 PM

Share this post


Link to post
Share on other sites
quote:


How useful would you find a string class or a linked list class that carried around a mutex that needed to be locked for every operation?

Very useful if I was sharing the string between processes, otherwise I'd wonder why they didn't use a critical section.


Actually it's kind of depressing to see people refer to strict Microsoft naming of MT primitives. I would say that mutex (MUTually EXclusive) is a wide concept including MS critical sections, MS mutex objects and MS semaphores. In other words critical sections are (a sort of) mutexes. And actually when reading a book strictly on concurrent programming, it would probably refer to them all as semaphores. So I wouldn't be to nit-picky about exact names...

quote:

But it's worse than that. If you pass a CWnd* from one thread to another, the second thread will not have access to the msg map for the WndProc... I'm not entirely certain what all the issues are, and they are notably evasive in the MSDN docs.
All I know is that I got a ton of assertion failures & crashes Can can work around it; you have to construct your CWnd in each thread you want to use it in from the native HWND. But then you're mutli-threading with Win32 handles not MFC objects.

If the only threading issue with MFC was a simple EnterCriticalSection(csMFC), it wouldn't be a big deal...


I'd say it's a big design-flaw in the code if you have more than one thread doing GUI-work.


Edited by - amag on February 8, 2001 9:37:30 AM

Share this post


Link to post
Share on other sites
quote:

I''d say it''s a big design-flaw in the code if you have more than one thread doing GUI-work.


Like a msg pump for the WinProc in one thread, and a second thread for Dx rendering (think Dx & windowed mode)?

And I thought semaphores were distinctly different than mutexes in that they allowed multiple items concurrent access, while a mutex allowed only one? Or is that purely a M$ concept?

Magmai Kai Holmlor
- The disgruntled & disillusioned

Share this post


Link to post
Share on other sites
quote:

Like a msg pump for the WinProc in one thread, and a second thread for Dx rendering (think Dx & windowed mode)?



Yes you are partly right, but I'd tend to see the message-pump as another Input-thread in this case, it doesn't do the actual rendering anymore, since that has moved to the DX-thread, right?


"And I thought semaphores were distinctly different than mutexes in that they allowed multiple items concurrent access, while a mutex allowed only one? Or is that purely a M$ concept?"

Actually a binary semaphore would allow only one access. The reason MS has separated the semaphore and critical section and mutex objects are simply effiency-reasons. The most commonly used semaphore is the binary and it can be greatly optimized by
using the CPU-instruction BTS (Bit Test and Set) (which indeed must be used by all mutex-primitives, but generic semaphores and mutex-objects require a bit more than that in order to work). Thus MS chooses to do a special implementation (which is perfectly sane, the opposite would be insane) for binary semaphores that they call critical sections. The same is with mutex-objects, since they allow inter-process synch, it would be too slow to put that in normal thread-synching primitives (although it is perfectly valid to use mutex-objects for thread-synchronisation), thus they add the mutex-object. All are specialised versions of the Concurrent Programming primitive semaphore. There are other primitive such as the monitor and the refined protected object that you can find in a language such as Ada 95 (not sure if it exists in Ada 83) and Java (using the synchronized specifier for methods). Those are not semaphores, but completly different primitives.



Edited by - amag on February 9, 2001 7:21:21 AM

Share this post


Link to post
Share on other sites
Right, a mutex IS a semaphore, with the maximum count set to one.

Interesting thing about monitors: they''re impossible to implement in Win95, but you can do it in NT. I''m by no means an expert in this, but from what I understand, monitors allow any logical expression (i.e. non-synchronization objects) to be treated as a synchronization condition. The trick is, in order to implement one, you must have (in win32 land) "SignalObjectAndWait".

I don''t recall the specifics of why this is necessary, this was from my NT threads programming course. But this isn''t supported in win95, only in winnt. NT also has "CreateWaitableTimer", which is too damn cool, but totally unportable to 95 or 98. Bummer.

Share this post


Link to post
Share on other sites
That''s because monitors support a type called a condition variable. Only threads waiting on a condition variable when it is signaled are wakened; if any threads then call wait immediately after the signal they still sleep. You usually must acquire a mutex prior to calling signal, and acquire a mutex before wait. The wait function atomically puts the thread to sleep and drops the mutex.

You can implement a condition variable in Win95, you don''t need WinNT''s SignalAndWait to do it (though it is a lot easier.)

I wouldn''t be too concerned about this though. Since Win32 event''s have memory, you can make minor mistakes, that would cost you on a condition variable, but would still work with events.

Share this post


Link to post
Share on other sites