Tricks of the Windows Game Programming Gurus:Algorithms, Data Structures, Memory Management, and Multithreading

All ITKnowledge Source Code Search Tips Advanced Search |

| Tricks of the Windows Game Programming Gurus by Andre LaMothe Sams, Macmillan Computer Publishing ISBN: 0672313618 Pub Date: 09/27/99

Search this book:

Previous | Table of Contents | Next

Sample output:

    Starting Threads…

    4 1 2 3 4 2 1 3 4 3 1 2 4 2 1 3 4 3 1 2 4 2 1 3 4 2

    3 1 4 2 1 3 4 2 3 1 4 2 3 1 4 2 3 1 4 2 3 1 4 2 3 1

    4 2 3 1 4 2 3 1 4 2 3 1 4 2 3 1 4 2 3 1 4 2 3 1 4 2

    3 1 4 2 3 1 4 2 3 1 4 2 3 1 4 2 3 1 4 2 3 1 2 3 1 3 2

    1 1 2 3 3 2 1 1 2 3 3 2 1 1 2 3 3 2 1 1 2 3 3 2 1 1 2

    3 3 2 1 2 3 1 3 2 1 2 3 1 3 2 1 2 3 1 3 2 1 2 3 1 3 2

    1 3 1 2 3 2 1 3 1 2 3 2 1

    All threads terminated.

As you can see from the sample output, when the user hits a key, all threads are terminated and the primary thread then terminates. There are two problems with this method. The first problem is subtle. Here’s the scenario; read it a couple of times to make sure you see the problem:

  1. Assume that all but one of the secondary threads has terminated.
  2. Assume that the last thread has processor control, and it decrements the global variable that tracks the number of active threads.
  3. At the instant this happens, there is a context switch to the primary process. It tests the global variable and thinks that all the threads have terminated, but the last thread still hasn’t returned!

In most cases this is not a problem, but it can be if there’s anything between the decrement code and the return code. What you need is a function that can query if a thread is terminated. This would help in many cases. There is a function group that waits for signals, referred to as the `Wait*()` group, that can help.

The second problem is that you’ve created what is called a busy loop, or a polling loop. This is normally fine in Win16/DOS, but in Win32 it’s a bad thing. Sitting in a tight loop, waiting on a variable, puts a lot of strain on the multitasking kernel and makes the CPU usage shoot way up.

To see this, you can use `SYSMON.EXE` (part of the Windows 95/98 accessories), `PERFMON.EXE` (part of Windows NT) or a similar third-party CPU usage utility. These utilities help you see what is happening with the threads and processor usage. Anyway, let’s look at how the `Wait*()` class of functions can help you determine if a thread has terminated.

#### Waiting for the Right Moment

Get ready for the most confusing explanation you’ve ever heard… but it’s not my fault, really! Whenever any thread terminates, it becomes signaled to the kernel, and when it is running, it is unsignaled. Whatever that means. And what is the price of plastic zippers tomorrow? You don’t care! But what you do care about is how to test for the signaling.

You can test for this event using the `Wait*()` class of functions, which allow you to test for a single signal (tongue twister) or multiple signals (does that sound sexual to you?). In addition, you can call one of the `Wait*()` functions to wait for the signal(s) until it happens, but without a busy loop. Much better than polling a global, in most cases. Figure 11.21 illustrates the mechanics of the `Wait*()` functions and their relationship to the running application and the OS kernel.

Figure 11.21 A timeline of signaling using `Wait*()`.

The two functions that you’re going to use are called `WaitForSingleObject()` and `WaitForMultipleObjects()`,which are used to wait for a single signal or multiple signals, respectively. Their definitions are

    DWORD WaitForSingleObject(HANDLE  hHandle,    // handle of

    object to wait for

              DWORD  dwMilliseconds);    // time-out interval

    in milliseconds

`hHandle` identifies the object.

`dwMilliseconds` specifies the time-out interval, in milliseconds. The function returns if the interval elapses, even if the object’s state is nonsignaled. If `dwMilliseconds` is zero, the function tests the object’s state and returns immediately. If `dwMilliseconds` is infinite, the function’s time-out interval never elapses.

If the function succeeds, the return value indicates the event that caused the function to return. If the function fails, the return value is `WAIT_FAILED`. To get extended error information, call `GetLastError()`.

The return value on success is one of the following values:

  • `WAIT_ABANDONED`—The specified object is a mutex object that was not released by the thread that owned it before the thread terminated. Ownership of the mutex object is granted to the calling thread, and the mutex is set to nonsignaled.
  • `WAIT_OBJECT_0`—The state of the specified object is signaled.
  • `WAIT_TIMEOUT`—The time-out interval has elapsed, and the object’s state is nonsignaled.

Basically, the `WaitForSingleObject()` function checks the current state of the specified object. If the object’s state is nonsignaled, the calling thread enters an efficient wait state. The thread consumes very little processor time while waiting for one of the conditions of the wait to be satisfied. And here is the function used to wait for multiple signals, or in this case multiple threads, to terminate:

    DWORD WaitForMultipleObjects(DWORD nCount, // number of handles

                                               // in handle array

             CONST HANDLE *lpHandles, // address of object-handle array

             BOOL  bWaitAll,    // wait flag

             DWORD  dwMilliseconds);  // time-out interval in milliseconds

`nCount` specifies the number of object handles in the array pointed to by `lpHandles`. The maximum number of object handles is `MAXIMUM_WAIT_OBJECTS`.

`lpHandles` points to an array of object handles. The array can contain handles of objects of different types. Note for Windows NT: The handles must have `SYNCHRONIZE` access.

`bWaitAll` specifies the wait type. If `TRUE`, the function returns when all objects in the `lpHandles` array are signaled at the same time. If `FALSE`, the function returns when any one of the objects is signaled. In the latter case, the return value indicates the object whose state caused the function to return.

`dwMilliseconds` specifies the time-out interval, in milliseconds. The function returns if the interval elapses, even if the conditions specified by the `bWaitAll` parameter are not satisfied. If `dwMilliseconds` is zero, the function tests the states of the specified objects and returns immediately. If `dwMilliseconds` is infinite, the function’s time-out interval never elapses.

Previous | Table of Contents | Next

Copyright © Macmillan Computer Publishing, Inc.

Products | Contact Us | About Us | Privacy | Ad Info | Home
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.