Hey everyone I was redirected here from Stack Overflow for this question.
I am completely new to thread synchronization using condition variables and pthreads in Linux.
Here is the code I wrote and I will explain the main idea behind it :
#include <pthread.h>
#include <iostream>
using namespace std;
pthread_cond_t bufferNotFull = PTHREAD_COND_INITIALIZER;
pthread_cond_t bufferNotEmpty = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
class Buffer {
private:
string *buffer;
int size;
int total;
int addIDX;
int getIDX;
public:
Buffer(int bufferSize) : size(bufferSize) {
this->buffer = new string[this->size];
this->total = 0;
this->addIDX = 0;
this->getIDX = 0;
}
virtual ~Buffer() {
delete[] this->buffer;
}
void addItem(const string &item) {
if (this->addIDX == this->size)
this->addIDX = 0;
this->buffer[this->addIDX] = item;
this->total++;
this->addIDX++;
}
string getItem() {
if (this->getIDX == this->size)
this->getIDX = 0;
string item = this->buffer[this->getIDX];
this->total--;
this->getIDX++;
return item;
}
bool isFull() { return (this->total == this->size); }
bool isEmpty() { return (this->total == 0); }
};
class Resource {
private:
int id;
int sum;
public:
Resource(int id) : id(id) { this->sum = 0; }
void updateSum(int num) { this->sum += num; }
void print() { cout << "Resource with ID " << this->id << " has sum : " << this->sum << endl; }
};
Buffer *buffer;
int totalResources = 6;
Resource **resourceManager = new Resource *[totalResources]{nullptr};
string randomMessage() {
// Generate a random message to be added to the buffer in the following format : ResourceID#ValueForSum
int randomResourceID = rand() % totalResources;
int randomValue = (rand() % 10) + 1;
return to_string(randomResourceID) + "#" + to_string(randomValue);
}
void decodeMessage(const string &message, string &resourceID, string &resourceValue) {
// Decode a message by storing each component in the appropriate variable
unsigned int j = 0;
while (message[j] != '#') {
resourceID.push_back(message[j]);
j++;
}
j++;
while (j != message.size()) {
resourceValue.push_back(message[j]);
j++;
}
}
void *consumer(void *argPtr) {
bool keepRunning = true;
while (keepRunning) {
pthread_mutex_lock(&mutex);
while (buffer->isEmpty())
pthread_cond_wait(&bufferNotEmpty, &mutex);
string itemRead = buffer->getItem();
string resourceID;
string resourceValue;
decodeMessage(itemRead, resourceID, resourceValue);
if (resourceValue == "QUIT_STRING")
keepRunning = false;
else
resourceManager[stoi(resourceID)]->updateSum(stoi(resourceValue));
pthread_cond_signal(&bufferNotFull);
pthread_mutex_unlock(&mutex);
}
pthread_exit(nullptr);
}
int main(int argc, char **argv) {
srand(time(nullptr));
int bufferSize = 3;
buffer = new Buffer(bufferSize);
for (int i = 0; i < totalResources; i++)
resourceManager[i] = new Resource(i);
int totalThreads = stoi(argv[1]);
pthread_t *threadIDS = new pthread_t[totalThreads];
for (int i = 0; i < totalThreads; i++)
pthread_create(&threadIDS[i], nullptr, consumer, nullptr);
volatile bool keepRunning = true;
string userCommand;
while (keepRunning) {
/* Get user command */
std::cout << "\nCommand : ";
getline(cin, userCommand);
/* Generate a random number of messages and insert them to bounded buffer */
if (userCommand == "/fill") {
int messagesToAdd = (rand() % 5) + 1;
std::string messages[messagesToAdd];
/* Print the messages generated to check for behavior afterwards */
for (int i = 0; i < messagesToAdd; i++) {
messages[i] = randomMessage();
cout << messages[i] + " ";
}
cout << endl;
for (unsigned int i = 0; i < messagesToAdd; i++) {
pthread_mutex_lock(&mutex);
while (buffer->isFull())
pthread_cond_wait(&bufferNotFull, &mutex);
string itemToAdd = messages[i];
buffer->addItem(itemToAdd);
pthread_cond_signal(&bufferNotEmpty);
pthread_mutex_unlock(&mutex);
}
}
/* Insert the message 0#QUIT_STRING in bounded buffer totalThreads times so that threads know that it's time to exit*/
else if (userCommand == "/exit") {
for (unsigned int i = 0; i < totalThreads; i++) {
pthread_mutex_lock(&mutex);
while (buffer->isFull())
pthread_cond_wait(&bufferNotFull, &mutex);
string itemToAdd = "0#QUIT_STRING";
buffer->addItem(itemToAdd);
pthread_cond_broadcast(&bufferNotEmpty);
pthread_mutex_unlock(&mutex);
}
keepRunning = false;
}
else
cout << "Unknown command" << endl;
}
for (int i = 0; i < totalThreads; i++)
pthread_join(threadIDS[i], nullptr);
delete buffer;
delete[] threadIDS;
for (int i = 0; i < totalResources; i++) {
/* Print the outcome for each resource */
resourceManager[i]->print();
delete resourceManager[i];
}
delete[] resourceManager;
pthread_cond_destroy(&bufferNotFull);
pthread_cond_destroy(&bufferNotEmpty);
pthread_mutex_destroy(&mutex);
}
The idea is to have a :
- A bounded/circular buffer
- The main thread that acts as a producer and adds items to the buffer
- Multiple "consumer" threads that work on and update shared resources after reading from buffer.
After multiple test cases the code above seems to work just fine with no race conditions and no threads left behind and therefore suspending the main thread forever.
However as I am new to this subject I was wondering if I am missing something and if there is a better way to do it.
Furthermore I was told on Stack Overflow that I should use things like std::condition_variable,std::thread and std::mutex but the thing is I am studying this subject for an upcoming assignment and these things or even simpler ones like std::vector are not allowed so any insight would be really useful.