Выбрать главу

  CradlePtr cradle;

public:

  DriveTrainRobot(CradlePtr cr) : cradle(cr) {}

  void run() {

    try {

      while(!Thread::interrupted()) {

        // Blocks until job is offered/accepted:

        cradle->offerDriveTrainBotServices();

        cout << "Installing DriveTrain" << endl;

        (*cradle)->addDriveTrain();

        cradle->taskFinished();

      }

    } catch(Interrupted_Exception&) { /* Exit */ }

    cout << "DriveTrainRobot off" << endl;

  }

};

class WheelRobot : public Runnable {

  CradlePtr cradle;

public:

  WheelRobot(CradlePtr cr) : cradle(cr) {}

  void run() {

    try {

      while(!Thread::interrupted()) {

        // Blocks until job is offered/accepted:

        cradle->offerWheelBotServices();

        cout << "Installing Wheels" << endl;

        (*cradle)->addWheels();

        cradle->taskFinished();

      }

    } catch(Interrupted_Exception&) { /* Exit */ }

    cout << "WheelRobot off" << endl;

  }

};

class Reporter : public Runnable {

  CarQueue carQueue;

public:

  Reporter(CarQueue& cq) : carQueue(cq) {}

  void run() {

    try {

      while(!Thread::interrupted()) {

        cout << carQueue->get() << endl;

      }

    } catch(Interrupted_Exception&) { /* Exit */ }

    cout << "Reporter off" << endl;

  }

};

int main() {

  cout << "Press <Enter> to quit" << endl;

  try {

    CarQueue chassisQueue(new TQueue<Car>),

             finishingQueue(new TQueue<Car>);

    CradlePtr cradle(new Cradle);

    ThreadedExecutor assemblyLine;

    assemblyLine.execute(new EngineRobot(cradle));

    assemblyLine.execute(new DriveTrainRobot(cradle));

    assemblyLine.execute(new WheelRobot(cradle));

    assemblyLine.execute(

      new Director(chassisQueue, finishingQueue, cradle));

    assemblyLine.execute(new Reporter(finishingQueue));

    // Start everything running by producing chassis:

    assemblyLine.execute(new ChassisBuilder(chassisQueue));

    cin.get();

    assemblyLine.interrupt();

  } catch(Synchronization_Exception& e) {

    cerr << e.what() << endl;

  }

} ///:~

You’ll notice that Car takes a shortcut: it assumes that bool operations are atomic, which, as previously discussed, is generally a safe assumption but one to consider carefully. Each Car begins as an unadorned chassis, and different robots will attach different parts to it, calling the appropriate "add" function when they do.

A ChassisBuilder simply creates a new Car every second and places it into the chassisQueue. A Director manages the build process by taking the next Car off the chassisQueue, putting it into the Cradle, telling all the robots to startWork( ), and suspending itself by calling waitUntilWorkFinished( ). When the work is done, the Director takes the Car out of the Cradle and puts in into the finishingQueue.

The Cradle is the crux of the signaling operations. A Mutex and a Condition object control both the working of the robots and indicate whether all the operations are finished. A particular type of robot can offer its services to the Cradle by calling the "offer" function appropriate to its type. At this point, that robot thread is suspended until the Director calls startWork( ), which changes the hiring flags and calls broadcast( ) to tell all the robots to show up for work. Although this system allows any number of robots to offer their services, each one of those robots has its thread suspended by doing so. You could imagine a more sophisticated system in which the robots register themselves with many different Cradles without being suspended by that registration process and then reside in a pool waiting for the first Cradle that needs a task completed.

After each robot finishes its task (changing the state of the Car in the process), it calls taskFinished( ), which sends a signal( ) to the readyCondition, which is what the Director is waiting on in waitUntilWorkFinished( ). Each time the director thread awakens, the state of the Car is checked, and if it still isn’t finished, that thread is suspended again.

When the Director inserts a Car into the Cradle, you can perform operations on that Car via the operator->( ). To prevent multiple extractions of the same car, a flag causes an error report to be generated. (Exceptions don’t propagate across threads in the ZThread library.)

In main( ), all the necessary objects are created and the tasks are initialized, with the ChassisBuilder begun last to start the process. (However, because of the behavior of the TQueue, it wouldn’t matter if it were started first.) Note that this program follows all the guidelines regarding object and task lifetime presented in this chapter, and so the shutdown process is safe.

Deadlock

Because threads can become blocked and because objects can have mutexes that prevent threads from accessing that object until the mutex is released, it’s possible for one thread to get stuck waiting for another thread, which in turn waits for another thread, and so on, until the chain leads back to a thread waiting on the first one. You get a continuous loop of threads waiting on each other, and no one can move. This is called deadlock.

If you try running a program and it deadlocks right away, you immediately know you have a problem, and you can track it down. The real problem is when your program seems to be working fine but has the hidden potential to deadlock. In this case, you may get no indication that deadlocking is a possibility, so it will be latent in your program until it unexpectedly happens to a customer. (And you probably won’t be able to easily reproduce it.) Thus, preventing deadlock through careful program design is a critical part of developing concurrent programs.

Let’s look at the classic demonstration of deadlock, invented by Edsger Dijkstra: the dining philosophers problem. The basic description specifies five philosophers (but the example shown here will allow any number). These philosophers spend part of their time thinking and part of their time eating. While they are thinking, they don’t need any shared resources, but when they are eating, they sit at a table with a limited number of utensils. In the original problem description, the utensils are forks, and two forks are required to get spaghetti from a bowl in the middle of the table, but it seems to make more sense to say that the utensils are chopsticks. Clearly, each philosopher will require two chopsticks in order to eat.