// // (Somewhat) simple program demonstrating the use of condition variables // to implement a "bounded buffer" of size N, with P producer // threads and C consumer threads. // Each producer thread will insert into the buffer M "messages" // (pairs of integers); consumer threads will read from the buffer // and print. // Consumer threads count how many buffer elements have been // consumed in all. // Main program waits for producers to finish and for the above // count to become zero, upon which it cancels the consumers. // // Command-line arguments are N, P, C, and M. // // In this version, the bounded buffer is implemented using condition // variables, and the main program "waits" for all messages to be // consumed in a simple dumb way (checks at intervals). // #include #include // has exit(), etc. #include // has pair class #include // has minus() #include // has pthread_ routines #include "threads-timer.h" // has timer() #include "threads-sharedVar.h" // has sharedVar class #include "threads-lock.h" // has lockObj class #include "threads-threadWrapper.h" // has threadWrapper class #include "threads-boundedBuffer-1.h"// has boundedBuffer class typedef pair intpair; // ---- Class for producer thread ------------------------------------ class producerThreadObj { public: typedef producerThreadObj * ptr; // member variables are parameters for this thread int myID; int M; boundedBuffer * bufferPtr; producerThreadObj(const int myID, const int M, boundedBuffer * const bufferPtr) { this->myID = myID; this->M = M; this->bufferPtr = bufferPtr; } // this member function is the code the thread should execute void run(void) { // Insert M messages with producer's ID # into buffer. for (int i = 0; i < M; ++i) bufferPtr->put(intpair(myID, i)); } }; // ---- Class for consumer thread ------------------------------------ class consumerThreadObj { public: typedef consumerThreadObj * ptr; // member variables are parameters for this thread int myID; boundedBuffer * bufferPtr; sharedVar * countPtr; // count left to consume lockObj * outputLockPtr; // lock for standard output consumerThreadObj(const int myID, boundedBuffer * const bufferPtr, sharedVar * const countPtr, lockObj * const outputLockPtr) { this->myID = myID; this->bufferPtr = bufferPtr; this->countPtr = countPtr; this->outputLockPtr = outputLockPtr; } // this member function is the code the thread should execute void run(void) { // Consume messages until interrupted by main program. while (true) { // Get the next message. intpair message = bufferPtr->get(); // Print it. outputLockPtr->lock(); cout << "consumer " << myID << " processing request " << message.second << " from producer " << message.first << endl; outputLockPtr->unlock(); // Update the counter. countPtr->safeUpdate(minus(), 1); } } }; // ---- Main program ------------------------------------------------- int main(int argc, char* argv[]) { // Process command-line arguments. if (argc < 5) { cerr << "Usage: " << argv[0] << " buffSize numProducers numConsumers numMessages\n"; exit(EXIT_FAILURE); } int N = atoi(argv[1]); int P = atoi(argv[2]); int C = atoi(argv[3]); int M = atoi(argv[4]); boundedBuffer buff(N); sharedVar leftToConsume(P*M); // number of messages left to consume lockObj outputLock; // Start P new threads for producers, each running a wrapper // function, with a producerThreadObj object as parameter. // (The wrapper function just invokes the object's "run()" method.) producerThreadObj::ptr * pthreadObj = new producerThreadObj::ptr[P]; // parameters for threads pthread_t * pthreads = new pthread_t[P]; // "handles" for threads for (int i = 0; i < P; ++i) { pthreadObj[i] = new producerThreadObj(i, M, &buff); if (pthread_create(&pthreads[i], NULL, threadWrapper::run, (void *) pthreadObj[i]) != 0) cerr << "Unable to create producer thread " << i << endl; } // Start C new threads for consumers, each running a wrapper // function, with a consumerThreadObj object as parameter. // (The wrapper function just invokes the object's "run()" method.) consumerThreadObj::ptr * cthreadObj = new consumerThreadObj::ptr[C]; // parameters for threads pthread_t * cthreads = new pthread_t[C]; // "handles" for threads for (int i = 0; i < C; ++i) { cthreadObj[i] = new consumerThreadObj(i, &buff, &leftToConsume, &outputLock); if (pthread_create(&cthreads[i], NULL, threadWrapper::run, (void *) cthreadObj[i]) != 0) cerr << "Unable to create consumer thread " << i << endl; } // Wait for producer threads to complete. for (int i = 0; i < P; ++i) { if (pthread_join(pthreads[i], NULL) != 0) cerr << "Unable to perform join on producer " << i << endl; } outputLock.lock(); cout << "Producer threads finished.\n"; outputLock.unlock(); // Now wait, in a clumsy way, for the consumers to finish their work. while (leftToConsume.safeRead() > 0) system("sleep 1"); // sleep 1 second and try again // Cancel consumer threads. for (int i = 0; i < C; ++i) { if (pthread_cancel(cthreads[i]) != 0) cerr << "Unable to perform cancel on consumer " << i << endl; } outputLock.lock(); cout << "Consumer threads cancelled.\n"; outputLock.unlock(); // Free everything allocated with "new" in this function. for (int i = 0; i < P; ++i) delete pthreadObj[i]; delete [] pthreadObj; delete [] pthreads; for (int i = 0; i < C; ++i) delete cthreadObj[i]; delete [] cthreadObj; delete [] cthreads; return EXIT_SUCCESS; }