Threads & Win32

Started by
8 comments, last by fungame 23 years, 6 months ago
Anyone here have any resources about multithreading, and some of the problems it might cause? I''ve never really used one before, even though I hear they can be icky, so I wanted to research. Also, I would like to know how to apply the info to windows; for instance, what is a critical section, or a mutex, and how do I use them? Thanks very much, FunGame
Advertisement
(In the spirit of one complaining of problems with a multi-threaded app elsewhere on the messageboard)

There not that yucky if you have the right knowledge... they''re really not THAT hard to use... I''ve heard they can be a speed hit (yes), but if your actual game runs very sporadically (sometimes fast, sometimes very slow) they can improve your performance. The key is that you won''t get better performance per-seill get smoother, more consistent performance.

I actually haven''t heard of a "muitex", but a critical section is VERY important. Basically, you declare your CS, initialize it, and "enter" it whenever you read shared data, then "leave" it when you''re done. When you kill the app, you delete it (like freeing memory). The reason for them is to keep your program from tripping over itself, supposedly from trying to read and write memory simultaneously (though I''m not sure how that would happen, actually)

--Tr][aD--
--Tr][aD--
O''Reilly''s Win32 Multithreading Programming is pretty good.
As far as I know, Win32 programs use what is called preemptive multitasking. Let me explain. When you compile your C/C++ code, it is changed into lots of smaller assembly code instructions. One line may take about 5, 10, 20 or more instructions to execute especially considering function calls. Now, several threads CANNOT be run simultaneously on your processor (unless you have a multiprocessor machine ). So, Windows keeps track of a list of the threads in the OS, and schedules them all for a very small time slice. It runs one for a bit, then switches to another, then another. And all of this takes place in a very small time frame, so small in fact that it _looks_ like all the threads/processes on your computer are running simultaneously.

Preemptive multitasking means that one thread/process can interrupt, or preempt another at almost any time. If you create two threads, your second thread may preempt the first thread in the middle of a line or perhaps even the middle of a statement. You could be in the middle of changing the value of a variable, and the second thread might read the partially changed value of the variable which is bad (you would effectively be reading random data).

Since you cannot know when the threads will preempt each other and/or your main process (the stuff in WinMain), you need to provide synchronization mechanisms. This will make sure that you can''t read and write the same data at the same time.

The synchronization objects in Windows are: critical sections, mutexes, semaphores, and events. Critical sections and mutexes are almost the same, except that mutexes are really only useful for interprocess communication (which most people don''t need) and therefore you should choose critical sections instead. Semaphores maintain more information than critical sections about how many threads are blocked, etc. which you probably don''t need. Events are just like BOOLs, except that you use Win32 to set/get their values and the OS protects them for you.

There should be a lot of good information in MSDN. Type CRITICAL_SECTION in the index and hit the Sync to Contents button. Then you can use the left view to check out the different papers and IIRC some examples, too.

Now, how does synchronization work? When you know your threads are going to be accessing the same data as your main process or simply communicating between themselves, you wrap the parts of your code that actually read/write the same data with calls to lock and unlock the synchronization object. Threads that access the same data should lock/unlock the _same_ critical section object.

By lock and unlock, I mean that you call the methods in the Win32 API (i.e, EnterCriticalSection, LeaveCriticalSection). When both threads try to call those methods on the same object, Windows will only let one thread execute. The other thread is "blocked" from executing until the first thread has released control of the critical section object. You can''t even know which thread will be blocked and which one will execute - it does however guarantee you that only one thread can access the data at any given time.

Note that your process is also a thread. In fact, it''s called the main thread of your program. You''ve been using a thread for a while now without knowing it!

Anyway, you want to make the code inside the EnterCriticalSection and LeaveCriticalSection as small as possible - just access the data and Leave. You want your threads to be "blocked" as little as possible. Although threads consume almost no processor time when they are blocked, they still aren''t running!

Now, of course, there are different approaches to the same problem. Why do you want to use multithreading in your app/game?


- null_pointer
Sabre Multimedia
I did a little experiement with a double-threaded app a few days ago. One thread was handling the data processing and one was doing data output. I got them working pretty well, and without using any special thread-related keywords or procedures, except the keyword volatile.

The code is on my computer at home, but I basically set up a system that allows the two threads each to use a separate set of the vital data. A single function takes a pair of void pointer arguments and pointers to each of the user-defined processing and output functions. It then starts a separate thread for each of the functions, using a shell coordinating function for each. The shell functions maintain a global volatile int variable that tells them whether they are on the same cycle, and if they aren''t, one waits for the other to catch up, using Sleep(0) to free up processor time for the other thread.

When the threads each finish. The processor shell function swaps the lists and each thread runs on the other list of data. Essentially, the display thread now reads and displays the data calculated by the processor thread, and the processor thread simultaneously begins processing the data for the next output. On a multiprocessor system, which I haven''t tested yet, it should run faster than having a single-threaded application. However, on my single-cpu system, I get quite a large overhead from switching threads and waiting for each to catch up. For example. running at around 30 - 60 thousand loops per second, the multithreaded version takes about 2-4 times as long as the single-threaded version. Running at "game speed" around 50-100 loops per second, I''m sure the thread overhead would be minimal.
Assassin, aka RedBeard. andyc.org
You''re basically doing by hand what critical sections do for you. And even if you don''t think you need to handle threads in an officially safe way, you should, because there are lots of problems associated with them... if they''re used incorrectly. I''ve had a few courses on concurrent computing (threads, multiprocessing, etc) and they can do wonderful things.

The best use of threads for a game or app would be to have the user interface run as one thread (which may further be split into a graphics and audio thread) and the logic run as another thread (which may be further split into various threads such as AI, data in/out, etc...) A scheme like this allows your app to use every piece of the processor it can get. Instead of having to write the logic to move from section to section (logic -> AI -> graphics -> audio -> user interface -> logic -> etc) you can put them all in their seperate threads. While this takes more care and work up front, it is the optimal (over the long haul) solution.

Why is it best? Because on an abstract level, your intention truly is to have all these components operating simultaneously. So, using threads you can program them to run that way (even if they don''t actually occur simultaneously on the processor).

Also, if you use threads your software will take advantage of multi-processors... a small niche, but a niche none the less.
Just a few points to be made in null''s article on the basics of multithreading.

quote:
Preemptive multitasking means that one thread/process can interrupt, or preempt another at almost any time. If you create two threads, your second thread may preempt the first thread in the middle of a line or perhaps even the middle of a statement.


A thread never preempts another thread. A timer interrupt periodically fires, allowing the OS to take control (that is, preempt the thread) and schedule a new thread to run.

quote:
Since you cannot know when the threads will preempt each other and/or your main process (the stuff in WinMain), you need to provide synchronization mechanisms. This will make sure that you can''t read and write the same data at the same time.


This is overly simplistic. The point is not for reading or writing data at the same time (which actually will usually work since most loads and stores are atomic.)

The point is that your code has a lot of invariants. For example, take a linked list. The invariant is that each node in the linked list points to the next valid node, etc. These invariants are violated during some of the operations on the linked lists. The point of mutual exclusion is to make sure no other thread can look at the linked list in an invalid state.

quote:
Events are just like BOOLs, except that you use Win32 to set/get their values and the OS protects them for you.

Events may be just like bool, or they may not (you can create events which are for all extents and purposes stateless.)
The main point of an event is that you can block, consuming zero CPU time until the event is fired. So if I want to wait for some other thread to finish working, I can check the status of a BOOL, looping until it becomes true (this is called a busy-wait). Or I can block on an event consuming no CPU, and allowing the other thread to finish quicker.
quote:Original post by mhkrause

quote:
--------------------------------------------------------------------------------

Since you cannot know when the threads will preempt each other and/or your main process (the stuff in WinMain), you need to provide synchronization mechanisms. This will make sure that you can''t read and write the same data at the same time.
--------------------------------------------------------------------------------

This is overly simplistic. The point is not for reading or writing data at the same time (which actually will usually work since most loads and stores are atomic.)


That''s a good point, but that was not exactly what I meant... To the person using the container, the container _is_ the data. If the data is a container, then the state of the container is just as important as the state of its data. Maybe it is a bit simplistic, but in my mind I don''t limit "data" to the intrinsic types or a chunk of memory - data can also be a container class or any other type of class.

Good clarification, though! And excellent points about the events and the technical stuff about preempting!


- null_pointer
Sabre Multimedia
Thanks =).

The reason I want to use multithreading is because I have a GUI to a game I am writing, and I was thinking about seperating the input stuff on to another thread so it is happening at the same time as the windows redrawing themselves, and everything else is happening.

Basically, all this thread would be doing is polling DirectInput, and sending my windows messages about what is going on.

Or I might seperate the drawing into another thread. I''m not sure if I ''need'' multithreading or not, because the GUI is largely unwritten yet, but I wanted to write it conscientious of the fact that I might want to use threads.

FunGame
In my project, I read the input (using DirectInput) with a timer. Basically, I''m using a multimedia timer that fires 50 times a second, instead of an explicitly separate thread. This way (I feel) I have more control of how often the input is read.

The actual input routine is quite short (though it does call DirectInput) so I''m fairly sure it''s not re-entrant. Once it''s read the keyboard state into an array, it finishes.

As I understand it, the main thread will never interrupt a timer event, but the timer may interrupt the main thread during a ''check key x'' function. As each key is stored in a single byte, I don''t see this as a problem.

Am I right? Or am I digging a big hole for myself?

Dave

This topic is closed to new replies.

Advertisement