Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions

  




 

 

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

Wait and signal

In ZThreads, the basic class that uses a mutex and allows task suspension is the Condition, and you can suspend a task by calling wait( ) on a Condition. When external state changes take place that might mean that a task should continue processing, you notify the task by calling signal( ), to wake up one task, or broadcast( ), to wake up all tasks that have suspended themselves on that Condition object.

There are two forms of wait( ). The first form takes an argument in milliseconds that has the same meaning as in sleep( ): pause for this period of time. The second form takes no arguments; this version is more commonly used. Both forms of wait( ) release the Mutex that is controlled by the Condition object and suspends the thread until that Condition object receives a signal( ) or broadcast( ). The first form may also terminate if it times out before a signal( ) or broadcast( ) is received.

Because wait( ) releases the Mutex, it means that the Mutex can be acquired by another thread. Thus, when you call wait( ) you re saying I ve done all I can right now so I m going to wait right here, but I want to allow other synchronized operations to take place if they can.

Typically, you use wait( ) when you re waiting for some condition to change that is under the control of forces outside the current function. (Often, this condition will be changed by another thread.) You don t want to idly loop while testing the condition inside your thread; this is called a busy wait, and it s usually a bad use of CPU cycles. Thus, wait( ) suspends the thread while waiting for the world to change, and only when a signal( ) or broadcast( ) occurs (suggesting that something of interest may have happened), does the thread wake up and check for changes. So wait( ) provides a way to synchronize activities between threads.

Let s look at a simple example. WaxOMatic.cpp has two processes: one to apply wax to a Car and one to polish it. The polishing process cannot do its job until the application process is finished, and the application process must wait until the polishing process is finished before it can put on another coat of wax. Both WaxOn and WaxOff use the Car object, which contains a Condition that it uses to suspend a thread inside waitForWaxing( ) or waitForBuffing( ):

//: C11:WaxOMatic.cpp {RunByHand}
// Basic thread cooperation.
//{L} ZThread
#include <iostream>
#include <string>
#include "zthread/Thread.h"
#include "zthread/Mutex.h"
#include "zthread/Guard.h"
#include "zthread/Condition.h"
#include "zthread/ThreadedExecutor.h"
using namespace ZThread;
using namespace std;
 
class Car {
Mutex lock;
Condition condition;
bool waxOn;
public:
Car() : condition(lock), waxOn(false) {}
void waxed() {
Guard<Mutex> g(lock);
waxOn = true; // Ready to buff
condition.signal();
}
void buffed() {
Guard<Mutex> g(lock);
waxOn = false; // Ready for another coat of wax
condition.signal();
}
void waitForWaxing() {
Guard<Mutex> g(lock);
while(waxOn == false)
condition.wait();
}
void waitForBuffing() {
Guard<Mutex> g(lock);
while(waxOn == true)
condition.wait();
}
};
 
class WaxOn : public Runnable {
CountedPtr<Car> car;
public:
WaxOn(CountedPtr<Car>& c) : car(c) {}
void run() {
try {
while(!Thread::interrupted()) {
cout << "Wax On!" << endl;
Thread::sleep(200);
car->waxed();
car->waitForBuffing();
}
} catch(Interrupted_Exception&) { /* Exit */ }
cout << "Ending Wax On process" << endl;
}
};
 
class WaxOff : public Runnable {
CountedPtr<Car> car;
public:
WaxOff(CountedPtr<Car>& c) : car(c) {}
void run() {
try {
while(!Thread::interrupted()) {
car->waitForWaxing();
cout << "Wax Off!" << endl;
Thread::sleep(200);
car->buffed();
}
} catch(Interrupted_Exception&) { /* Exit */ }
cout << "Ending Wax Off process" << endl;
}
};
 
int main() {
cout << "Press <Enter> to quit" << endl;
try {
CountedPtr<Car> car(new Car);
ThreadedExecutor executor;
executor.execute(new WaxOff(car));
executor.execute(new WaxOn(car));
cin.get();
executor.interrupt();
} catch(Synchronization_Exception& e) {
cerr << e.what() << endl;
}
} ///:~
 

In Car s constructor, a single Mutex is wrapped in a Condition object so that it can be used to manage inter-task communication. However, the Condition object contains no information about the state of your process, so you need to manage additional information to indicate process state. Here, Car has a single bool waxOn, which indicates the state of the waxing-polishing process.

In waitForWaxing( ), the waxOn flag is checked, and if it is false, the calling thread is suspended by calling wait( ) on the Condition object. It s important that this occur inside a guarded clause, where the thread has acquired the lock (here, by creating a Guard object). When you call wait( ), the thread is suspended and the lock is released. It is essential that the lock be released because, to safely change the state of the object (for example, to change waxOn to true, which must happen if the suspended thread is to ever continue), that lock must be available to be acquired by some other task. In this example, when another thread calls waxed( ) to tell it that it s time to do something, the mutex must be acquired in order to change waxOn to true. Afterward, waxed( ) sends a signal( ) to the Condition object, which wakes up the thread suspended in the call to wait( ). Although signal( ) may be called inside a guarded clause as it is here you are not required to do this.[158]

In order for a thread to wake up from a wait( ), it must first reacquire the mutex that it released when it entered the wait( ). The thread will not wake up until that mutex becomes available.

The call to wait( ) is placed inside a while loop that checks the condition of interest. This is important for two reasons:[159]

      It is possible that when the thread gets a signal( ), some other condition has changed that is not associated with the reason that we called wait( ) here. If that is the case, this thread should be suspended again until its condition of interest changes.

      By the time this thread awakens from its wait( ), it s possible that some other task has changed things such that this thread is unable or uninterested in performing its operation at this time. Again, it should be re-suspended by calling wait( ) again.

Because these two reasons are always present when you are calling wait( ), always write your call to wait( ) inside a while loop that tests for your condition(s) of interest.

WaxOn::run( ) represents the first step in the process of waxing the car, so it performs its operation (a call to sleep( ) to simulate the time necessary for waxing). It then tells the car that waxing is complete, and calls waitForBuffing( ), which suspends this thread with a wait( ) until the WaxOff process calls buffed( ) for the car, changing the state and calling notify( ). WaxOff::run( ), on the other hand, immediately moves into waitForWaxing( ) and is thus suspended until the wax has been applied by WaxOn and waxed( ) is called. When you run this program, you can watch this two-step process repeat itself as control is handed back and forth between the two threads. When you press the <Enter> key, interrupt( ) halts both threads when you call interrupt( ) for an Executor, it calls interrupt( ) for all the threads it is controlling.

Thinking in C++ Vol 2 - Practical Programming
Prev Home Next

 
 
   Reproduced courtesy of Bruce Eckel, MindView, Inc. Design by Interspire