Threads Part 2: You are not a Jedi yet

By Dan Haines & Dave Navarro, Jr.

Synchronization, That's the Ticket

When running multiple threads in your program, sometimes it is important to synchronize the efforts of individual threads. If each thread is unaware of the others, important data might be corrupted.

For example, you may have a program which allows the user to print a document in the background using a separate thread. If the user selects Exit from the menu or pushes the close button for the application, you don't want to just end the program. You want to give the user an opportunity to gracefully abort printing, or allow it to finish before closing the application. Some way is needed to allow the different threads of your program to communicate with each other.

The most common method used is to create a Global variable which can be seen by all of the threads in your program. It might be just an Integer flag which has a zero or non-zero value to indicate if a thread is running. Or it might be an array of some kind where messages from a primary thread are stored for a worker thread to process (kind of like the out-box on your bosses desk). Each thread looks at or modifies the data in these global variables and the values of these variables determine what a thread does. If there is a global variable called Printing which has a non-zero value, then the primary thread knows that there is an active thread which is sending data to the printer. As soon as Printing changes to zero then your program can safely allow itself to end.

The problem with using Global variables is that any thread can change the variable's value at any time. If, for example, two threads are created to print separate documents, it's possible for one thread to change Printing to zero before the second thread finishes. You either need Printing to be a count of the total number of threads, or you need some method of locking the Printing variable so that it can't be changed to zero until all threads are finished. You need a Semaphore.

Semaphores

Semaphores are special variables which are created and managed by Windows. When our printing thread starts, it can allocate a semaphore called "print". Windows will then create a variable with that name, internally. When additional threads begin, instead of creating new variables called "print", Windows will use the existing variable and increment its value. When each thread ends it calls Windows to remove the "print" variable from memory. Windows will not remove it from memory until its value reaches zero (all threads have ended). Semaphores are perfect for letting threads keep track of each other. But they're not very useful for sharing data.

Sharing Data

Occasionally, multiple threads may need to share data with each other. In our word processor we want to be able to print documents and edit them at the same time. Keeping two complete copies of the document in memory is very wasteful. A better method would be to keep a single copy of the document in memory and have each thread store only the changes it has made. This way our printing thread can print the document as it was when the printing operation started, but our editing thread can make changes to the document while the original is printing.

In PB/DLL any variables which are declared as GLOBAL can be seen by all threads in our application. Therefore, if we create a global string array to store the text of our document, both our editing thread and our printing thread can access it. When printing begins, we simply set a semaphore to indicate printing has begun. The editing thread, seeing the print semaphore is active will then store all changes to the document in a separate array until printing is complete. Once all printing has finished, it can then move the changes back into the master array.

Why Jedis?

In case you haven't noticed, the sub-titles of this series are based on George Lucas' Star Wars ® movies. Some of you may be wondering why. Besides the cute factor, it's actually to make a point. Like the Jedi art, Basic programming in Windows has been scoffed at. After all, everyone knows that all serious programming is done in C++. Basic is just not a robust or powerful enough language.

Those of us who write Basic programs for a living know that this simply isn't true. C++ is much like the dark side. Sure, it's very inviting and seems like it's more powerful and easier, but it's not. In truth, modern Basic compilers like PowerBASIC (and to some extent, Visual Basic) make it easier than ever to write powerful applications for Windows. While Microsoft has some catching up to do in terms of supporting multi-threading, built-in sorting, etc., the combination of VB and PB/DLL allow Basic programmers to accomplish any task that can be done in C++.