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

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:

·         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.

Producer-consumer relationships

A common situation in threading problems is the producer-consumer relationship, in which one task is creating objects and other tasks are consuming them. In such a situation, make sure that (among other things) the consuming tasks do not accidentally skip any of the produced objects.

To show this problem, consider a machine that has three tasks: one to make toast, one to butter the toast, and one to put jam on the buttered toast.

//: C11:ToastOMatic.cpp

// Problems with thread cooperation.

//{L} ZThread

#include "zthread/Thread.h"

#include "zthread/Mutex.h"

#include "zthread/Guard.h"

#include "zthread/Condition.h"

#include "zthread/ThreadedExecutor.h"

#include <iostream>

#include <cstdlib>

#include <ctime>

using namespace ZThread;

using namespace std;

// Apply jam to buttered toast:

class Jammer : public Runnable {

  Mutex lock;

  Condition butteredToastReady;

  bool gotButteredToast;

  int jammed;

public:

  Jammer(): butteredToastReady(lock) {

    gotButteredToast = false;

    jammed = 0;

  }

  void moreButteredToastReady() {

    Guard<Mutex> g(lock);

    gotButteredToast = true;

    butteredToastReady.signal();

  }

  void run() {

    try {

      while(!Thread::interrupted()) {

        {

          Guard<Mutex> g(lock);

          while(!gotButteredToast)

            butteredToastReady.wait();

          jammed++;

        }

        cout << "Putting jam on toast " << jammed << endl;

        {

          Guard<Mutex> g(lock);

          gotButteredToast = false;

        }

      }

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

    cout << "Jammer off" << endl;

  }

};

// Apply butter to toast:

class Butterer : public Runnable {

  Mutex lock;

  Condition toastReady;

  CountedPtr<Jammer> jammer;

  bool gotToast;

  int buttered;

public:

  Butterer(CountedPtr<Jammer>& j)

  : jammer(j), toastReady(lock) {

    gotToast = false;

    buttered = 0;

  }

  void moreToastReady() {

    Guard<Mutex> g(lock);

    gotToast = true;

    toastReady.signal();

  }

  void run() {

    try {

      while(!Thread::interrupted()) {

        {

          Guard<Mutex> g(lock);

          while(!gotToast)

            toastReady.wait();

          buttered++;

        }

        cout << "Buttering toast " << buttered << endl;

        jammer->moreButteredToastReady();

        {

          Guard<Mutex> g(lock);

          gotToast = false;

        }

      }

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

    cout << "Butterer off" << endl;

  }

};

class Toaster : public Runnable {

  CountedPtr<Butterer> butterer;

  int toasted;

public:

  Toaster(CountedPtr<Butterer>& b) : butterer(b) {

    toasted = 0;

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

  }

  void run() {

    try {

      while(!Thread::interrupted()) {

        Thread::sleep(rand()/(RAND_MAX/5)*100);

        // ...

        // Create new toast