bufferMon.cpp

URL: https://mirkwood.cs.edinboro.edu/~bennett/class/cmsc4000/spring2026/notes/ch6/code/bufferMon.cpp
 
#include <iostream>

#include <semaphore>
#include <thread>

#include <vector>
#include<queue>

using namespace std;

const int MAX_SIZE{5};

// this implements the PrintMonitor as a singleton.
// The code is ugy mostly because of the singleton
// This relies on somewhat advanced C++ knowledge
class PrintMonitor {
    public: 
         // no public constructor or assignment operator or copy constructor
         PrintMonitor(const PrintMonitor& ) = delete; 
         PrintMonitor & operator = (const PrintMonitor & ) = delete;

         // this is a strange function
         // It will get an instance of the monitor
         //    If it does not exist, it will make one and return it
         //    If it does exist, it will return it.
         // This provides some level of guarentee that we will not have two.
         static PrintMonitor& Get() {
             static PrintMonitor instance;
             return instance;
         }

         // the entire purpose of this class, a  protected print function
         void Print(string message) {
            outputMutex.acquire();
            cout << message << endl;
            outputMutex.release();
         }

    private:
        // but a private constructor, it can be called in Get()
        PrintMonitor() = default;
        ~PrintMonitor() = default;

        binary_semaphore outputMutex{1};
};

// this is not a singleton, it should be but I don't want to make it more
//  complex than we need.
//  
//  This contins the semaphores and the buffer so that no work with these will
//  be done outside of the class
//
//  It removes most of the global variables.
//  A global instance of BufferMonitor will need to be declared
//  But if it were a singleton, that would not be necessary either.

class BufferMonitor {
   public:
       void  PostMessage(string message, int id) {
           queueEmpty.acquire();
           queueMutex.acquire();
           Q.push(message);

           emptyCount --;
           fullCount ++;
           PrintMonitor::Get().Print(to_string(id) 
                 + "\t\tPosted a message, empty " 
                 + to_string(emptyCount) +  " full " + to_string(fullCount));

           queueMutex.release();
           queueFull.release();
       }

       string GetMessage(int id ) {
           queueFull.acquire();
           queueMutex.acquire();
    
           emptyCount ++;
           fullCount --;
           PrintMonitor::Get().Print("\t" + to_string(id)
                 + "\tGot a message, empty "
                 + to_string(emptyCount)  +  " full " + to_string(fullCount));

           string message = Q.front();
           Q.pop();

           queueMutex.release();
           queueEmpty.release();

           return message;
       }

   private:
      binary_semaphore queueMutex{1};
      queue<string> Q;
      counting_semaphore<MAX_SIZE> queueEmpty{MAX_SIZE};
      counting_semaphore<MAX_SIZE> queueFull{0};
      int emptyCount {MAX_SIZE};
      int fullCount{0};
};

BufferMonitor bufMon;

void Consumer(int id);
void Producer(int id, int iterations);

int main() {

   int producers{4};
   int consumers{3};
   int messages{6};
   vector<thread> producerThreads;
   vector<thread> consumerThreads;

   // make the producers
   for(int i =0; i < producers; ++i) {
       producerThreads.emplace_back(Producer, i, messages);
   }

   // make the consumers
   for(int i =0; i < consumers; ++i) {
       consumerThreads.emplace_back(Consumer, i+100);
   }

   // collect all of the producers
   for(auto & thread: producerThreads) {
       // joinable says this is a thread the could "join" or go away
       // This should be true for all of the threads
       if(thread.joinable()) {
           // .join is blocking
           thread.join();
       }
   }

   // send a message to all of the consumers to get them to exit
   for(int i = 0; i < consumers; ++i) {
       bufMon.PostMessage("done", 0);
   }

   // collect all of the consumers
   for(auto & thread: consumerThreads) {
       if(thread.joinable()) {
           thread.join();
       }
   }

   return(0);
}

void Consumer(int id) {
   bool done{false};
   string message;

   while(not done) {
      PrintMonitor::Get().Print("\t" + to_string(id) + "\t is sleeping ");
      usleep(rand() % 1000);


      PrintMonitor::Get().Print("\t" + to_string(id) 
                      + "\t is getting a message ");
      message = bufMon.GetMessage(id);
      if (message == "done") {
         done = true;
         PrintMonitor::Get().Print("\t" + to_string(id) 
                      + "\t got \"done\", is exiting");
      } else {
         PrintMonitor::Get().Print("\t" + to_string(id) 
                      + "\t got \"" + message + '"' );
      }
   }
}


void Producer(int id, int iterations) {
    string message;

    for(int i = 0; i < iterations; ++i) {
      PrintMonitor::Get().Print(to_string(id) + " is sleeping ");
      usleep(rand() % 500);
  
      message = "message " + to_string(i+1) + " from " + to_string(id);
      PrintMonitor::Get().Print("\t" + to_string(id) 
                                           + " is posting a message ");
      bufMon.PostMessage(message, id);
    }
    PrintMonitor::Get().Print("\t" + to_string(id) + " is all done, byby");
}