Multi-Threading for Basic Programmers
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++.
Don't miss a single episode!
In our next installment,
Part 3: May the "Power" be with You, we'll take a close look at the
built-in PB/DLL support for threads and actually create a multi-threaded DLL
for use with Visual Basic.
Did you miss Part 1? Read it here:
Part 1: Use the Force, Luke.
|