The tasks are placed on the TQueue by main( ) and are taken off the TQueue by the LiftOffRunner. Notice that LiftOffRunner can ignore the synchronization issues because they are solved by the TQueue.

<p>Proper toasting</p>

To solve the ToastOMatic.cpp problem, we can run the toast through TQueues between processes. And to do this, we will need actual toast objects, which maintain and display their state:

//: C11:ToastOMaticMarkII.cpp

// Solving the problems using TQueues

//{L} ZThread

#include "zthread/Thread.h"

#include "zthread/Mutex.h"

#include "zthread/Guard.h"

#include "zthread/Condition.h"

#include "zthread/ThreadedExecutor.h"

#include "TQueue.h"

#include

#include

#include

#include

using namespace ZThread;

using namespace std;

class Toast {

  enum Status { dry, buttered, jammed };

  Status status;

  int id;

public:

  Toast(int idn) : id(idn), status(dry) {}

  void butter() { status = buttered; }

  void jam() { status = jammed; }

  string getStatus() const {

    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 > 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),

butteredQueue(new TQueue),

finishedQueue(new TQueue);

    cout << "Press 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.

<p>Broadcast</p>

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( ).

Перейти на страницу:

Похожие книги