#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"); }