Get a site

VC++ 6.0 ebook chapter index
Free counters!

Thread Synchronization

About once a year, the traffic lights at the busy intersection outside my apartment window stop working. The result is chaos, and while the cars usually avoid actually hitting each other, they often come close.

We might term the intersection of two roads a "critical section." A southbound car and a westbound car cannot pass through an intersection at the same time without hitting each other. Depending on the traffic volume, different approaches are taken to solve the problem. For light traffic at an intersection with high visibility, drivers can be trusted to properly yield. More traffic might require a stop sign, and still heavier traffic would require traffic lights. The traffic lights help coordinate the activity of the intersection (as long as they work, of course).

The Critical Section

In a single-tasking operating system, traditional computer programs don't need traffic lights to help them coordinate their activities. They run as if they owned the road, which they do. There is nothing to interfere with what they do.

Even in a multitasking operating system, most programs seemingly run independently of each other. But some problems can arise. For example, two programs could need to read from and write to the same file at the same time. In such cases, the operating system provides a mechanism of shared files and record locking to help out.

However, in an operating system that supports multithreading, the situation gets messy and potentially dangerous. It is not uncommon for two or more threads to share some data. For example, one thread could update one or more variables and another thread could use those variables. Sometimes this poses a problem, and sometimes it doesn't. (Keep in mind that the operating system can switch control from one thread to another between machine code instructions only. If only a single integer is being shared among the threads, then changes to this variable usually occur in a single instruction and potential problems are minimized.)

However, suppose that the threads share several variables or a data structure. Often, these multiple variables or the fields of the structure must be consistent among themselves. The operating system could interrupt a thread in the middle of updating these variables. The thread that uses these variables would then be dealing with inconsistent data.

The result is a collision, and it's not difficult to imagine how an error like this could crash the program. What we need are the programming equivalents of traffic lights to help coordinate and synchronize the thread traffic. That's the critical section. Basically, a critical section is a block of code that should not be interrupted.

There are four functions for using critical sections. To use these functions, you must define a critical section object, which is global variable of type CRITICAL_SECTION. For example,


This CRITICAL_SECTION data type is a structure, but the fields are used only internally to Windows. This critical section object must first be initialized by one of the threads in the program by calling

InitializeCriticalSection (&cs) ;

This creates a critical section object named cs. The online documentation for this function includes the following warnings: "A critical section object cannot be moved or copied. The process must also not modify the object, but must treat it as logically opaque." This can be translated as "Don't mess around with it, and don't even look at it."

After the critical section object has been initialized, a thread enters a critical section by calling

EnterCriticalSection (&cs) ;

At this point, the thread is said to "own" the critical section object. No two threads can own the critical section object at the same time. Thus, if another thread has entered a critical section, the next thread calling EnterCriticalSection with the same critical section object will be suspended in the function call. The function will return only when the first thread leaves the critical section by calling

LeaveCriticalSection (&cs) ;

At that time, the second thread—suspended in its call to EnterCriticalSection—will own the critical section and the function call will return, allowing the thread to proceed.

When the critical section object is no longer needed by the program, it can be deleted by calling

DeleteCriticalSection (&cs) ;

This frees up any system resources that might have been allocated to maintain the critical section object.

This critical section mechanism involves "mutual exclusion," a term that will come up again as we continue to explore thread synchronization. Only one thread can own a critical section at any time. Thus, one thread can enter a critical section, set the fields of a structure, and exit the critical section. Another thread using the structure would also enter a critical section before accessing the fields of the structure and then exit the critical section.

Note that you can define multiple critical section objects—for example, cs1 and cs2. If a program has four threads and the first two threads share some data, they can use one critical section object, and if the other two threads share some other data, they can use a second critical section object.

Also note that you should be careful when using a critical section in your main thread. If the secondary thread spends a long time in its own critical section, it could hang the main thread for an inordinate amount of time. The secondary thread would probably just want to use the critical section to copy the fields of the structure to its own local variables.

One limitation with critical sections is that they can be used for coordinating threads within a single process only. But there are cases where you need to coordinate two different processes that share a resource (such as shared memory). You can't use critical sections for that; instead, you must use something oddly called a "mutex object." The fabricated word "mutex" stands for "mutual exclusion," and that's precisely the goal here. You want to prevent threads of a program from being interrupted while updating or using some shared memory or other resources.