Daily Archives: April 30, 2002

Threads in MFC II: Synchronization Objects

Introduction

In part I, I looked at getting threads communicating with each other. Now let’s look at how we can manage how multiple threads operate on single objects.

Let’s take an example. Suppose we have a global variable (or any variable that is accessible to two or more threads via scope, pointers, references, whatever). Let’s say this variable object is a CStringArray called stringArray
.

Now, let’s suppose our main thread wants to add something to the array. Fine enough. We can do that. Then, let’s throw in a second thread which can somehow access this object. It, too, wants to access stringArray . What would happen if both threads tried to simultaneously write to the first position in the array for example? Or even if one were just reading and the other writing? Well, if there is no synchronization between the two threads, you don’t know what would happen. The result is completely unpredicatable. One thread would write some bytes to memory, while another reads it, and you could have the correct answer or the wrong answer or a mix. Or it could crash. Who knows…

You can’t even assume safety when merely reading an object from two threads. Even if it seems like no bytes are changing, and both threads should get valid results, you have to think about a lower level: A single C++ statement compiles to many assembly or machine language instructions. These instructions directly access the processor, including the registers that keep track of where we are, what data we’re looking at. It’s possible to have one of those registers hold a pointer to the current character in the string, so if you have two threads that rely on that pointer in that register–they are obviously not both going to be correct except in a very rare circumstance.

OK, I think I’ve made the case. How do we control access to objects then?

Windows has a number of synchronization objects that you can use to effectively prevent accidents. MFC encapsulates these into CEvent , CCriticalSection, CMutex , and CSemaphore . To use these, include afxmt.hin your project.

CEvent

Let’s start with these so-called triggers. An event in this context is nothing more than a flag, a trigger. Imagine it as cocking a gun (Reset) and then firing it (Set). You can use events for setting of threads. Here’s how.
Remember how we created a structure that contained all the data we wanted to send the thread? Let’s add a new one. First create a CEvent object in the dialog (or any window or non-window) class called m_event . Now, in our [code lang=”cpp”]THREADINFOSTRUCT [/code], let’s add a pointer to an event:

[code lang=”cpp”]
typedef struct THREADINFOSTRUCT {

CEvent* pEvent;

} THREADINFOSTRUCT;
[/code]

When we initialize the structure, we must do the assignment:

[code lang=”cpp”]tis->pEvent=&m_event; [/code]

In our thread function, we call:

[code lang=”cpp”]tis->pEvent->Lock(); [/code]

This will “lock” on the event (the same event that is in our dialog class in the main thread). The thread will effectively stop. It will loop inside of CEvent::Lock() until that event is “Set.” Where do you set it? In the main thread. An event is initially reset–cocked. Create the thread. When you want the thread to unblock itself and continue, you call m_event.Set()–fire the gun.

So what are some practical examples? You could lock a thread before you access a global object. In your main thread, when you’re done using that object, you call Set(). You can also use an event to signal a thread to exit (such as if
you hit an abort button in the main thread). To see an example of this usage, look at the demo project I’ve uploaded to the code tool section.

There are two types of events: ones that automatically reset when you set them, and ones that don’t.

You can use a single event to trigger multiple threads, but the event had better be a manual-reset event or only one thread will be triggered at a time.

CCriticalSection

These are pretty simple to use. You simply surround every usage of the shared object by a lock and an unlock command:

[code lang=”cpp”]CCriticalSection cs;

cs.Lock();
stringArray.DoSomething();
cs.Unlock();
[/code]
Do that in every thread that uses that object. You must use the same critical section variable to lock the same object. If a thread tries to lock an object that’s already locked, it will just sit there waiting for it to unlock so it can safely access the object.
CMutex

A mutex works just like a critical section, but it can also work across different processes. But you don’t want to always use mutexes, because they are slower than critical sections.
You can declare a mutex like this:

[code lang=”cpp”]CMutex m_mutex(FALSE, “MyMutex”); [/code]
The first parameter specifies whether or not the mutex is initially locked or not. The second parameter is the identifier of the mutex so it can be accessed from two different processes.

If you lock a critical section in a thread and then the thread exits without unlocking it, then any other thread waiting on it will be forever blocked. Mutexes, however, will unlock automatically if the thread exits. Mutexes can
also have a time-out value (critical sections can too, but there are some doubts as to whether or not they work–perhaps the bugs are fixed in MFC 7.0).

Otherwise, it works the same:

[code lang=”cpp”]
m_muytex.Lock(60000);//time out in milliseconds
stringArray.DoSomething();
m_mutex.Unlock();
[/code]

CSemaphore

A semaphore is used to limit simultaneous access of a resource to a certain number of threads. Most commonly, this resource is a pool of a certain number of limited resources. If we had ten string arrays, we could set up a semaphore to guard them and let only ten threads at a time access them. Or COM ports, internet connections, or anything else.

It’s declared like this:

[code lang=”cpp”]CSemaphore m_semaphore(10,10); [/code]

The first argument is the initial reference count, while the second is the maximum reference count. Each time we lock the semaphore, it will decrement the reference count by 1, until it reaches zero. If another thread tries to lock the semaphore, then it will just go into a holding pattern until a thread unlocks it.

As with a mutex, you can pass it a time-out value.

It’s used with the same syntax:

[code lang=”cpp”]
m_semaphore.Lock(60000);
stringArray.DoSomething();
m_semaphore.Unlock();
[/code]

Conclusion

These two tutorials, along with the sample projects, should be enough to get you started using threads. There are a couple of other MFC objects and issues that I have yet to cover, so I’ll group all of these into Part III of this
tutorial. These topics include exception-handling and thread-safe classes. Make sure that you examine the documentation of all of these classes: there is more functionality than I could cover in this short tutorial. And if you really want to learn threads, get a good book that covers the Windows kernel (one called Programming Applications for MS Windows comes to mind, published by Microsoft).

The sample project for this tutorial has a time object that it shares between two threads. It’s protected by a critical section. There are also two events: for starting the thread and aborting it. The main thread uses a timer to add the current time to a list box every second, while the thread traces the current time to the debug window and sends a message to the main thread to remove the first time from the list. The thread is only started after the time
hits an even ten-second boundary.

©2004 Ben Watson