Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
The previous example shows a fundamental problem when using
threads: You never know when a thread might be run. Imagine sitting at a table
with a fork, about to spear the last piece of food on a platter, and as your
fork reaches for it, the food suddenly vanishes (because your thread was
suspended and another diner came in and ate the food). That s the problem
you re dealing with when writing concurrent programs.
Occasionally you don t care if a resource is being accessed
at the same time you re trying to use it. But in most cases you do care, and
for multithreading to work, you need some way to prevent two threads from
accessing the same resource, at least during critical periods.
Preventing this kind of collision is simply a matter of
putting a lock on a resource when one thread is using it. The first thread that
accesses a resource locks it, and then the other threads cannot access that
resource until it is unlocked, at which time another thread locks and uses it,
and so on. If the front seat of the car is the limited resource, the child who
shouts Dibs! acquires the lock.
Thus, we need to be able to prevent any other tasks from
accessing the storage when that storage is not in a proper state. That is, we
need to have a mechanism that excludes a second task from accessing the
storage when a first task is already using it. This idea is fundamental to all
multithreading systems and is called mutual exclusion; the mechanism used abbreviates this to mutex. The ZThread library contains a
mutex mechanism declared in the header Mutex.h.
To solve the problem in the above program, we identify the critical sections where mutual exclusion must apply; then we acquire
the mutex before entering the critical section and release it at the end
of the critical section. Only one thread can acquire the mutex at any time, so
mutual exclusion is achieved:
//: C11:MutexEvenGenerator.cpp {RunByHand}
// Preventing thread collisions with mutexes.
//{L} ZThread
#include <iostream>
#include "EvenChecker.h"
#include "zthread/ThreadedExecutor.h"
#include "zthread/Mutex.h"
using namespace ZThread;
using namespace std;
class MutexEvenGenerator : public Generator {
unsigned int currentEvenValue;
Mutex lock;
public:
MutexEvenGenerator() { currentEvenValue = 0; }
~MutexEvenGenerator() {
cout << "~MutexEvenGenerator"
<< endl;
}
int nextValue() {
lock.acquire();
++currentEvenValue;
Thread::yield(); // Cause failure faster
++currentEvenValue;
int rval = currentEvenValue;
lock.release();
return rval;
}
};
int main() {
EvenChecker::test<MutexEvenGenerator>();
} ///:~
MutexEvenGenerator adds a Mutex called lock
and uses acquire( ) and release( ) to create a critical
section within nextValue( ). In addition, a call to Thread::yield( )
is inserted between the two increments, to raise the likelihood of a
context switch while currentEvenValue is in an odd state. Because the
mutex prevents more than one thread at a time in the critical section, this
will not produce a failure, but calling yield( ) is a helpful way
to promote a failure if it s going to happen.
Note that nextValue( ) must capture the return
value inside the critical section because if you return from inside the
critical section, you won t release the lock and will thus prevent it from
being acquired again. (This usually leads to deadlock, which you ll
learn about at the end of this chapter.)
The first thread that enters nextValue( )
acquires the lock, and any further threads that try to acquire the lock are
blocked from doing so until the first thread releases the lock. At that point,
the scheduling mechanism selects another thread that is waiting on the lock.
This way, only one thread at a time can pass through the code that is guarded
by the mutex.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |