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

    switch(status) {

      case dry: return "dry";

      case buttered: return "buttered";

      case jammed: return "jammed";

      default: return "error";

    }

  }

  int getId() { return id; }

  friend ostream& operator<<(ostream& os, const Toast& t) {

    return os << "Toast " << t.id << ": " << t.getStatus();

  }

};

typedef CountedPtr< TQueue<Toast> > ToastQueue;

class Toaster : public Runnable {

  ToastQueue toastQueue;

  int count;

public:

  Toaster(ToastQueue& tq) : toastQueue(tq), count(0) {

    srand(time(0)); // Seed the random number generator

  }

  void run() {

    try {

      while(!Thread::interrupted()) {

        int delay = rand()/(RAND_MAX/5)*100;

        Thread::sleep(delay);

        // Make toast

        Toast t(count++);

        cout << t << endl;

        // Insert into queue

        toastQueue->put(t);

      }

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

    cout << "Toaster off" << endl;

  }

};

// Apply butter to toast:

class Butterer : public Runnable {

  ToastQueue dryQueue, butteredQueue;

public:

  Butterer(ToastQueue& dry, ToastQueue& buttered)

  : dryQueue(dry), butteredQueue(buttered) {}

  void run() {

    try {

      while(!Thread::interrupted()) {

        // Blocks until next piece of toast is available:

        Toast t = dryQueue->get();

        t.butter();

        cout << t << endl;

        butteredQueue->put(t);

      }

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

    cout << "Butterer off" << endl;

  }

};

// Apply jam to buttered toast:

class Jammer : public Runnable {

  ToastQueue butteredQueue, finishedQueue;

public:

  Jammer(ToastQueue& buttered, ToastQueue& finished)

  : butteredQueue(buttered), finishedQueue(finished) {}

  void run() {

    try {

      while(!Thread::interrupted()) {

        // Blocks until next piece of toast is available:

        Toast t = butteredQueue->get();

        t.jam();

        cout << t << endl;

        finishedQueue->put(t);

      }

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

    cout << "Jammer off" << endl;

  }

};

// Consume the toast:

class Eater : public Runnable {

  ToastQueue finishedQueue;

  int counter;

public:

  Eater(ToastQueue& finished)

  : finishedQueue(finished), counter(0) {}

  void run() {

    try {

      while(!Thread::interrupted()) {

        // Blocks until next piece of toast is available:

        Toast t = finishedQueue->get();

        // Verify that the toast is coming in order,

        // and that all pieces are getting jammed:

        if(t.getId() != counter++ ||

           t.getStatus() != "jammed") {

          cout << ">>>> Error: " << t << endl;

          exit(1);

        } else

          cout << "Chomp! " << t << endl;

      }

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

    cout << "Eater off" << endl;

  }

};

int main() {

  try {

    ToastQueue dryQueue(new TQueue<Toast>),

butteredQueue(new TQueue<Toast>),

finishedQueue(new TQueue<Toast>);

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

    ThreadedExecutor executor;

    executor.execute(new Toaster(dryQueue));

    executor.execute(new Butterer(dryQueue,butteredQueue));

    executor.execute(

      new Jammer(butteredQueue, finishedQueue));

    executor.execute(new Eater(finishedQueue));

    cin.get();

    executor.interrupt();

  } catch(Synchronization_Exception& e) {

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

  }

} ///:~

Two things are immediately apparent in this solution: first, the amount and complexity of code within each Runnable class is dramatically reduced by the use of the TQueue, because the guarding, communication, and wait( )/signal( ) operations are now taken care of by the TQueue. The Runnable classes don’t have Mutexes or Condition objects anymore. Second, the coupling between the classes is eliminated because each class communicates only with its TQueues. Notice that the definition order of the classes is now independent. Less code and less coupling is always a good thing, which suggests that the use of the TQueue has a positive effect here, as it does on most problems.

Broadcast

The signal( ) function wakes up a single thread that is waiting on a Condition object. However, multiple threads may be waiting on the same condition object, and in that case you’ll want to wake them all up using broadcast( ) instead of signal( ).

As an example that brings together many of the concepts in this chapter, consider a hypothetical robotic assembly line for automobiles. Each Car will be built in several stages, and in this example we’ll look at a single stage: after the chassis has been created, at the time when the engine, drive train, and wheels are attached. The Cars are transported from one place to another via a CarQueue, which is a type of TQueue. A Director takes each Car (as a raw chassis) from the incoming CarQueue and places it in a Cradle, which is where all the work is done. At this point, the Director tells all the waiting robots (using broadcast( )) that the Car is in the Cradle ready for the robots to work on it. The three types of robots go to work, sending a message to the Cradle when they finish their tasks. The Director waits until all the tasks are complete and then puts the Car onto the outgoing CarQueue to be transported to the next operation. In this case, the consumer of the outgoing CarQueue is a Reporter object, which just prints the Car to show that the tasks have been properly completed.