Multithreading - Part 1 by (21 February 2000) |
Return to The Archives |
Introduction
|
Multithreading.
What a terrifying word. Not one month ago the thought of writing a multithreaded application made my brain hurt and my skin crawl.
Fortunately, it turns out the skin crawling was a completely unrelated medical issue and that multithreading is actually quite easy, provided you're
careful in your implementation. In this tutorial I'll show you a few of the places to use multithreading, and give you everything you need to
run threads. Lastly, I'll show you how to let two or more threads access the same piece of information without screwing each other up. All subsequent code was written for Win32 in C++ using Microsoft Developer Studio 6.0. |
When To Use Multithreading
|
There are thousands of possible applications for multithreading, it's just a question of picking the right time and place. A good example of where to use multithreading would be an editor that is doing some complicated work and you'd like to show a progress meter with a cancel button. In Windows anything done to a window has to be passed to the window procedure and this usually involves calling GetMessage(), TranslateMessage(), and DispatchMessage(). The problem: if your single threaded program is hip-deep in some very tedious calculations it won't be able to stop in the middle to call those three functions. The result is that you might be able to click the cancel button but the message would never reach the window procedure, so the program wouldn't be able to stop. Now if you wanted to do things the counter-intuitive way, you might want to copy GetMessage(), TranslateMessage() and DispatchMessage() all over the place in your calculations. Of course, you'd still have to add in some code to help you escape from calculation. Starting to sound a little messy? The solution is to use two threads: one to process the window messages and one to do the calculating. While the calculation thread is running it can report it's progress to the message processing thread which can then update the progress meter. If the cancel button is pushed, the message reached the window procedure almost immediately and then the calculating thread is killed. |
What About In Games?
|
Good question. So far I've only found one place that multithreading is critical and that's in a game's network code. Imagine that your game is drawing 25 frames a second. Most of that time will be spent rendering, which means that nearly 40 milliseconds is spent doing things other than networking. So imagine if a message arrives from the internet just as the game starts rendering. the network code will read in the message 40ms later and that's an automatic gain of 40ms on the ping time. If the same thing happens on the other computer then we're looking at an automatic addition of 80ms to the ping time. Barf. |
Overview Of How Multithreading Works (As I Understand It)
|
Each thread running in your computer is allocated a certain amount of memory and a certain amount of time to use the CPU. Threads have priority levels and the higher priority threads use more CPU time. Windows will automatically try to manage the threads to give each one the time it needs based on the priority level but you can control your threads to a certain degree in order to make things run better. Unless told to, threads don't care what other threads are doing, so it's quite possible that two threads might try to use the same piece of data at the same time and never know that they were messing with each other's stuff, leading to potential disaster. This kind of data-used-by-many-threads is often refered to as critical section data. A thread that plays nicely with the other threads and does not try to muck with a critical section while another thread is using it is refered to as thread safe. |
Alright already! Let's See Some Thread Code!
|
Let's start by designing a thread class. This first version of the class will not be thread safe. The declaration looks as follows.
But what does it mean? I think this is one of those cases where it's easier to show first, explain later. Might as well start at the beginning!
Wow. Is your head hurting yet? (Why yes, I am a patronizing bastard. How did you know?) |
cThread::Begin()
|
CreateThread(). It's that easy. Basically what it says is "create a thread that runs as long as cThreadProc is running, pass 'this' to cThreadProc (as a void *), do not start the thread paused (NULL), and give us an ID number for this thread (d_threadID)". CreateThread() returns a handle that can be used to do things to the thread. If CreateThread() returns null then the thread was not created. The #ifs are an added bonus - by carefully placing them throughout the code we can turn off all the multithreading just by changing Project->Settings->C++->Code Generation->Run-time library. That means if you want multithreading you'll have to set the run-time libraries to the multithreaded version, which prevents third party developers (or even you) from making a silly mistake weeks from now. |
cThreadProc()
|
Ooh ooh ooh, I love the way cThreadProc works, mainly because it's so damn clever (and I got to
steal the idea). Like most Windows functions that ask you to pass a function pointer, cThreadProc should
NOT be a class method. But we want to work with a lot of threads, so just one cThreadProc won't
do! The solution is to pass a pointer to our cThread class to cThreadProc as follows.
I don't know about you, but I think that's pretty sweet. Incidentally, the same thing could be done if you wanted to write a Win32 wrapper for windows and dialog boxes. It's a little bit harder to implement but the principle is the same. A side benefit is that this forces us to make cThread::ThreadProc() a public function. Why is this beneficial? Because if the application is compiled in single threaded mode, there still has to be a way to call the thread procedure. Speaking of which...
What? Why is it so empty? Because cThread is never meant to be used 'as is', it has to be derived. On a side note: any value returned by ThreadProc() can be retrieved by calling
|
cThread::End()
|
WaitForSingleObject() will pause the thread until d_threadHandle is no longer being used by Windows, which only happens when cThread::ThreadProc() is finished. Guess how long it waits? INFINITE. You might think that since ThreadProc() can end without calling End() that it might lead to disaster, but notice the next line, CloseHandle()? the d_threadHandle will be valid until End() is called. So there you have it! Derive cThread and go crazy! Just remember that if a two threads use the same data, they'd better not access that data at the same time. ...And how do we guarantee that? Tune in next week! Created: Dan Royer aka "Aggrav8d" Contact: aggravated@bigfoot.com Visit : http://members.home.com/droyer/index.html Something mysterious is formed, born in the silent void. Waiting alone and unmoving, it is at once still and yet in constant motion. It is the source of all programs. I do not know its name, so I will call it the Tao of Programming. |
Article Series:
|