Search code examples
multithreadingc++11concurrencycondition-variablefizzbuzz

C++11 concurrency; condition_variable; fizz, buzz, fizzbuzz; STUCK


I am unable to find any bug, the program doesn't show any output; however if I introduce a cout with some print in number() just after while I see some output, are signals getting missed?

I have launched 4 threads, all are using unique_lock on a common mutex and a condition variable. The same logic works if I use semaphores but when I converted them to c++11 condition_variable I am seeing no output on the screen. The threads are hung and are waiting for signals. But the thread[3] should run because the condition for which it is on wait if true; curr=1.

#include<iostream>
#include<thread>
#include<mutex>  
using namespace std; 

class FizzBuzz {
private:
    int n;
    int curr;
    std::mutex mtx; 
    std::condition_variable cv;

public:
    FizzBuzz(int n) {
        this->n = n;
        curr = 1;
    }

    // printFizz() outputs "fizz".
      void fizz(function<void()> printFizz) {
        while(curr <= n) {
            std::unique_lock<std::mutex> lck(mtx);
            cv.wait(lck, [&]{return (curr <= n && curr % 3 == 0 && curr % 5 != 0);});
            printFizz();
            curr++;
            lck.unlock();
            cv.notify_all();
        }
    }

    // printBuzz() outputs "buzz".
      void buzz(function<void()> printBuzz) {
        while(curr <= n) {
            std::unique_lock<std::mutex> lck(mtx);
            cv.wait(lck, [&]{return (curr <= n && curr % 3 != 0 && curr % 5 == 0);});
            printBuzz();
            curr++;
            lck.unlock();
            cv.notify_all();
        }
    }

    // printFizzBuzz() outputs "fizzbuzz".
      void fizzbuzz(function<void()> printFizzBuzz) {
        while(curr <= n) {
            std::unique_lock<std::mutex> lck(mtx);
            cv.wait(lck, [&]{return (curr <= n && curr % 3 == 0 && curr % 5 == 0);});
            printFizzBuzz();
            curr++;
            lck.unlock();
            cv.notify_all();
        }
    }

    // printNumber(x) outputs "x", where x is an integer.
      void number(function<void(int)> printNumber) {
        while(curr <= n) {
            std::unique_lock<std::mutex> lck(mtx);
            cv.wait(lck, [&]{return (curr <= n && curr % 3 != 0 && curr % 5 != 0);});
            printNumber(curr);
            curr++;
            lck.unlock();
            cv.notify_all();
        }
    }
};
void printFizz (void) {
  cout << "Fizz";
};
void printBuzz (void) {
  cout << "Buzz";
};
void printFizzBuzz (void) {
  cout << "FizzBuzz";
};
void printNumber (int n) {
  cout << n;
};

int main(int argc, char* argv[]) {
  int num;

  sscanf(argv[1], "%d", &num);

  std::function<void(void)> f_fizz     = printFizz;
  std::function<void(void)> f_buzz     = printBuzz;
  std::function<void(void)> f_fizzbuzz = printFizzBuzz;
  std::function<void(int)>  f_num      = printNumber;
  FizzBuzz *objPtr = new FizzBuzz(num);

  std::thread threads[4];


  threads[0] = std::thread(&FizzBuzz::fizz, objPtr ,f_fizz);
  threads[1] = std::thread(&FizzBuzz::buzz,objPtr, f_buzz);
  threads[2] = std::thread(&FizzBuzz::fizzbuzz,objPtr, f_fizzbuzz);
  threads[3] = std::thread(&FizzBuzz::number,objPtr, f_num);

  for (auto& th: threads) th.join();
  return 0;
}

Solution

  • The notify_all reaches other threads, but the condition curr <=n && ...is not satisfied. Therefore, the other threads continue to wait and hang the program.

    For example, the following code would terminate properly, because setting the terminated flag will wake all threads and have them break the loop.

    #include<iostream>
    #include<thread>
    #include<mutex>  
    #include <functional>
    #include <condition_variable>
    using namespace std; 
    
    class FizzBuzz {
    private:
        int n;
        int curr;
        std::mutex mtx; 
        std::condition_variable cv;
        bool terminated;
    
    public:
        FizzBuzz(int n) {
            this->n = n;
            curr = 1;
            terminated = false;
        }
    
        // printFizz() outputs "fizz".
          void fizz(function<void()> printFizz) {
            while(curr <= n) {
                std::unique_lock<std::mutex> lck(mtx);
                cv.wait(lck, [&]{return (terminated || (curr % 3 == 0 && curr % 5 != 0));});
                if (terminated)
                    break;
                printFizz();
                curr++;
                lck.unlock();
                cv.notify_all();
            }
            cout << "fizz exiting" << endl;
            terminated = true;
        }
    
        // printBuzz() outputs "buzz".
          void buzz(function<void()> printBuzz) {
            while(curr <= n) {
                std::unique_lock<std::mutex> lck(mtx);
                cv.wait(lck, [&]{return (terminated || (curr % 3 != 0 && curr % 5 == 0));});
                if (terminated)
                    break;
                printBuzz();
                curr++;
                lck.unlock();
                cv.notify_all();
            }
            cout << "buzz exiting" << endl;
            terminated = true;
        }
    
        // printFizzBuzz() outputs "fizzbuzz".
          void fizzbuzz(function<void()> printFizzBuzz) {
            while(curr <= n) {
                std::unique_lock<std::mutex> lck(mtx);
                cv.wait(lck, [&]{return (terminated || (curr % 3 == 0 && curr % 5 == 0));});
                if (terminated)
                    break;
                printFizzBuzz();
                curr++;
                lck.unlock();
                cv.notify_all();
            }
            cout << "fizzbuzz exiting" << endl;
            terminated = true;
        }
    
        // printNumber(x) outputs "x", where x is an integer.
          void number(function<void(int)> printNumber) {
            while(curr <= n) {
                std::unique_lock<std::mutex> lck(mtx);
                cv.wait(lck, [&]{return (terminated || (curr % 3 != 0 && curr % 5 != 0));});
                if (terminated)
                    break;
                printNumber(curr);
                curr++;
                lck.unlock();
                cv.notify_all();
            }
            cout << "number exiting" << endl;
            terminated = true;
        }
    };
    void printFizz (void) {
      cout << "Fizz" << endl;
    };
    void printBuzz (void) {
      cout << "Buzz" << endl;
    };
    void printFizzBuzz (void) {
      cout << "FizzBuzz" << endl;
    };
    void printNumber (int n) {
      cout << n << endl;
    };
    
    int main(int argc, char* argv[]) {
      int num;
    
      sscanf(argv[1], "%d", &num);
    
      std::function<void(void)> f_fizz     = printFizz;
      std::function<void(void)> f_buzz     = printBuzz;
      std::function<void(void)> f_fizzbuzz = printFizzBuzz;
      std::function<void(int)>  f_num      = printNumber;
      FizzBuzz *objPtr = new FizzBuzz(num);
    
      std::thread threads[4];
    
    
      threads[0] = std::thread(&FizzBuzz::fizz, objPtr ,f_fizz);
      threads[1] = std::thread(&FizzBuzz::buzz,objPtr, f_buzz);
      threads[2] = std::thread(&FizzBuzz::fizzbuzz,objPtr, f_fizzbuzz);
      threads[3] = std::thread(&FizzBuzz::number,objPtr, f_num);
    
      for (auto& th: threads) {
          th.join();
      }
      return 0;
    }