I am implementing the ‘producer-consumer problem’ in C, which solves the synchronization problem of redundant production, redundant consumption, and consuming unproduced items when multi-threading.
bool expected = false;
while (!atomic_compare_exchange_weak(&lock, &expected, true))
expected = false;
In order to prevent duplicate access of the process, the atomic_compare_exchange_weak was used.
I know that the atomic_compare_exchange_weak can block access of another process when one process enters critical section. However, duplicate access of the process continues to occur. How can I solve this problem? Below is my producer and consumer code.
producer code
void *producer(void *arg) {
int i = *(int *)arg;
int item;
while(alive) {
while(counter >= BUFSIZE);
expected_p = false;
while (!atomic_compare_exchange_weak(&lock_p, &expected_p, true))
expected_p = false;
item = next_item++;
buffer[in] = item;
in = (in + 1) % BUFSIZE;
counter++;
if (task_log[item][0] == -1) {
task_log[item][0] = i;
produced++;
lock_p = false;
}
else {
printf("ERROR\n");
continue;
}
printf("<P%d,%d>", i, item);
}
pthread_exit(NULL);
}
consumer code
void *consumer(void *arg)
{
int i = *(int *)arg;
int item;
while (alive) {
while (counter <= 0);
expected_c = false;
while (!atomic_compare_exchange_weak(&lock_c, &expected_c, true))
expected_c = false;
item = buffer[out];
out = (out + 1) % BUFSIZE;
counter--;
if (task_log[item][0] == -1) {
printf("ERROR\n");
continue;
}
else if (task_log[item][1] == -1) {
task_log[item][1] = i;
consumed++;
lock_c = false;
}
else {
printf("ERROR\n");
continue;
}
printf("<C%d,%d>\n", i, item);
}
pthread_exit(NULL);
}
global value
int buffer[BUFSIZE];
int in = 0;
int out = 0;
int counter = 0;
int next_item = 0;
bool expected_p = false;
bool expected_c = false;
atomic_bool lock_p = false;
atomic_bool lock_c = false;
>Solution :
Just use a pthread_cond and pthread_mutex instead of trying to do something crazy with atomics. The code below may have more overhead using a real lock that an atomic primitive, but it’s far safer and more maintainable. Further, the condition variable in place of your spin loops will increase performance of both threads dramatically – overcoming the overhead of using a real lock.
Declare a mutex and a pair of condition variables:
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_can_write, cand_can_read;
Then start both threads and modify as follows:
void *producer(void *arg) {
int i = *(int *)arg;
int item;
while(alive) {
pthread_mutex_lock(&mut); // TAKE THE LOCK
// wait for counter to go below BUFSIZE
while (counter >= BUFSIZE) {
// atomically release lock and wait for consumer thread to signal
pthread_cond_wait(&cond_can_write, &mut);
// when pthread_cond_wait returns, this thread has reacquired the lock
}
// DO YOUR PRODUCE LOGIC HERE
pthread_mutex_unlock(&mut); // EXIT THE LOCK
// signal to consumer that it can read
pthread_cond_broadcast(&cond_can_read);
}
pthread_exit(NULL);
}
void *consumer(void *arg)
{
int i = *(int *)arg;
int item;
while (alive) {
pthread_mutex_lock(&mut); // TAKE THE LOCK
// Wait for the counter to go above 0
while (counter <= 0) {
// atomically release lock and wait for producer thread to signal
pthread_cond_wait(&cond_can_read, &mut);
// when pthread_cond_wait returns, this thread has reacquired the lock
}
// DO YOUR CONSUME LOGIC HERE
pthread_mutex_unlock(&mut); // EXIT THE LOCK
// signal to producer that it can write again
pthread_cond_broadcast(&cond_write);
}
pthread_exit(NULL);
}