So I have this lab problem where I need to use 2 threads with mutex and cond var to print the title pattern. The pattern is stored in an array of char, and there's a global keeping track of which index to print. In my code I created my logic was this
thread A created first, so thread A runs first and locks the mutex so only A should be running and B should be asleep
A enters while loop and prints A0, increments index, then using pthread_cond_wait(), unlocks mutex and sleeps and waits for signal cord
B runs and prints next sequence then sends signal and Thread A wakes up and pthread_cond_wait() upon return relocks mutex so Thread A has control back and B sleeps
This continues and prints the pattern sequence. But when I run it I just get B0B1B2B3B4B5B6B7B8B9. I don't know where I went wrong and would like insight, thanks
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int string_index = 0;
char string_to_print[10] = "0123456789";
pthread_mutex_t lock;
pthread_cond_t cond;
void *funcA(void *arg)
{
pthread_mutex_lock(&lock); //lock mutex
while (1)
{
printf("A%c", string_to_print[string_index]);
string_index++;
pthread_cond_wait(&cond, &lock); //sleep and wait for signal
}
}
void *funcB(void *arg)
{
while (1)
{
pthread_mutex_lock(&lock); //lock mutex
printf("B%c", string_to_print[string_index]);
string_index++;
if(string_index > 9)
{
string_index = 0;
printf("\n");
}
pthread_mutex_unlock(&lock); //unlock mutex
pthread_cond_signal(&cond); //send cond var signal
}
}
int main(void)
{
//initialize mutex lock
if (pthread_mutex_init(&lock, NULL) != 0)
{
printf("\n mutex init has failed\n");
return 1;
}
//initialize conditional variable
if (pthread_cond_init(&cond, NULL) != 0)
{
printf("cond var init failed");
return 1;
}
pthread_t A,B;
//create thread A
if (pthread_create(&A, NULL, &funcA, NULL) != 0)
{
printf("Error creating thread");
exit(-1);
}
//create thread B
if (pthread_create(&B, NULL, &funcB, NULL) != 0)
{
printf("Error creating thread");
exit(-1);
}
sleep(20); /* sleep 20 secs to allow time for the threads to run */
/* before terminating them with pthread_cancel() */
pthread_cancel(A);
pthread_cancel(B);
pthread_exit(NULL);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
So I have this lab problem where I need to use 2 threads with mutex and cond var to print the title pattern. The pattern is stored in an array of char, and there's a global keeping track of which index to print. In my code I created my logic was this
thread A created first, so thread A runs first and locks the mutex so only A should be running and B should be asleep
A enters while loop and prints A0, increments index, then using pthread_cond_wait(), unlocks mutex and sleeps and waits for signal cord
B runs and prints next sequence then sends signal and Thread A wakes up and pthread_cond_wait() upon return relocks mutex so Thread A has control back and B sleeps
This continues and prints the pattern sequence. But when I run it I just get B0B1B2B3B4B5B6B7B8B9. I don't know where I went wrong and would like insight, thanks
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int string_index = 0;
char string_to_print[10] = "0123456789";
pthread_mutex_t lock;
pthread_cond_t cond;
void *funcA(void *arg)
{
pthread_mutex_lock(&lock); //lock mutex
while (1)
{
printf("A%c", string_to_print[string_index]);
string_index++;
pthread_cond_wait(&cond, &lock); //sleep and wait for signal
}
}
void *funcB(void *arg)
{
while (1)
{
pthread_mutex_lock(&lock); //lock mutex
printf("B%c", string_to_print[string_index]);
string_index++;
if(string_index > 9)
{
string_index = 0;
printf("\n");
}
pthread_mutex_unlock(&lock); //unlock mutex
pthread_cond_signal(&cond); //send cond var signal
}
}
int main(void)
{
//initialize mutex lock
if (pthread_mutex_init(&lock, NULL) != 0)
{
printf("\n mutex init has failed\n");
return 1;
}
//initialize conditional variable
if (pthread_cond_init(&cond, NULL) != 0)
{
printf("cond var init failed");
return 1;
}
pthread_t A,B;
//create thread A
if (pthread_create(&A, NULL, &funcA, NULL) != 0)
{
printf("Error creating thread");
exit(-1);
}
//create thread B
if (pthread_create(&B, NULL, &funcB, NULL) != 0)
{
printf("Error creating thread");
exit(-1);
}
sleep(20); /* sleep 20 secs to allow time for the threads to run */
/* before terminating them with pthread_cancel() */
pthread_cancel(A);
pthread_cancel(B);
pthread_exit(NULL);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
}
Share
Improve this question
asked yesterday
SliferslackerSliferslacker
433 bronze badges
5
|
1 Answer
Reset to default 1thread A created first, so thread A runs first
The fact that thread A was created first does not necessarily mean that thread A will be the first thread to lock the mutex. It is possible for thread B to be faster.
If you want to ensure that thread A does something before thread B, you must make thread B wait for thread A to be finished doing that thing (for example using pthread_cond_wait
).
B runs and prints next sequence then sends signal and Thread A wakes up
Thread A will not be able to awaken until the condition varible is signaled AND the mutex is unlocked.
Thread B locks the mutex again a very short time after unlocking it. Mutexes are not guaranteed to be fair, and are often implemented in such a way that they favor performance over fairness. Therefore, you cannot rely on Thread A having an opportunity to acquire the mutex.
If you want to ensure that thread A gets an opportunity to wake up, you must make thread B wait for thread A (for example using pthread_cond_wait
).
In order to ensure that the threads take turns in printing one character, I suggest that you add a variable named turn
to your program which indicates which thread's turn it is to print the next character. This variable should be protected by the mutex. You should also add a second condition variable, so that each thread has its own condition variable to wait on.
Another issue in your code is that your cleanup code in the main thread is not being executed, because you have a pthread_exit
function call beforehand. I am not sure if this is intentional.
Your cleanup code should have calls to pthread_join
even if those threads have been canceled with pthread_cancel
. Otherwise, you will have a resource leak. Another reason to use pthread_join
is that you should not attempt to destroy the mutex or the condition variable(s) while one of the threads is still running.
Even if you wait for the threads to complete using pthread_join
, it is possible that one of the threads will terminate while leaving the mutex in a locked state. See this answer to another question for further information. This will cause the function call to pthread_mutex_destroy
to invoke undefined behavior. This is because according to POSIX, calling pthread_mutex_destroy
on a locked mutex invokes undefined behavior.
In order to prevent this undefined behavior from being possibly invoked, it would probably be better to use a different mechanism for stopping the threads than pthread_cancel
, because it is difficult to use pthread_cancel
in such a way that the mutex gets unlocked before the thread terminates. Therefore, instead of using pthread_cancel
, I suggest that the main thread sets a variable to tell the other threads to stop. I do not recommend protecting this variable with the mutex, because the main thread may not be able to acquire the mutex in a timely manner, since the other threads are constantly contending for the mutex. Instead, I recommend an atomic variable.
After doing the above, the code will look like this:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdatomic.h>
int turn = 0;
atomic_bool terminate = false;
int string_index = 0;
char string_to_print[10] = "0123456789";
pthread_mutex_t lock;
pthread_cond_t condA, condB;
void *funcA(void *arg)
{
pthread_mutex_lock(&lock); // lock mutex
while (1)
{
if (terminate)
{
pthread_mutex_unlock(&lock);
pthread_cond_signal(&condB);
pthread_exit(NULL);
}
if (turn == 0)
{
printf("A%c", string_to_print[string_index]);
string_index++;
turn = 1;
pthread_cond_signal(&condB); //send cond var signal
}
pthread_cond_wait(&condA, &lock); //sleep and wait for signal
}
}
void *funcB(void *arg)
{
pthread_mutex_lock(&lock); //lock mutex
while (1)
{
if (terminate)
{
pthread_mutex_unlock(&lock);
pthread_cond_signal(&condA);
pthread_exit(NULL);
}
if (turn == 1)
{
printf("B%c", string_to_print[string_index]);
string_index++;
if(string_index > 9)
{
string_index = 0;
printf("\n");
}
turn = 0;
pthread_cond_signal(&condA); //send cond var signal
}
pthread_cond_wait(&condB, &lock); //sleep and wait for signal
}
}
int main(void)
{
//initialize mutex lock
if (pthread_mutex_init(&lock, NULL) != 0)
{
printf("\n mutex init has failed\n");
return 1;
}
//initialize conditional variables
if (pthread_cond_init(&condA, NULL) != 0)
{
printf("cond var init failed");
return 1;
}
if (pthread_cond_init(&condB, NULL) != 0)
{
printf("cond var init failed");
return 1;
}
pthread_t A,B;
//create thread A
if (pthread_create(&A, NULL, &funcA, NULL) != 0)
{
printf("Error creating thread");
exit(-1);
}
//create thread B
if (pthread_create(&B, NULL, &funcB, NULL) != 0)
{
printf("Error creating thread");
exit(-1);
}
sleep(20);
// set this atomic variable instead of using pthread_cancel
terminate = true;
//pthread_cancel(A);
//pthread_cancel(B);
pthread_join(B, NULL);
pthread_join(A, NULL);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&condB);
pthread_cond_destroy(&condA);
}
This code has the following output:
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
A0B1A2B3A4B5A6B7A8B9
[...]
As you can see, the threads are now reliably taking turns in printing characters.
It may be worth noting that if you are sure that you have at least two hardware threads, you can also use a single atomic variable for thread synchronization, instead of mutexes and condition variables. That way, the threads will busy-wait instead of falling asleep:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdatomic.h>
atomic_int turn = 0;
int string_index = 0;
char string_to_print[10] = "0123456789";
void *funcA(void *arg)
{
while (1)
{
switch ( atomic_load_explicit( &turn, memory_order_acquire ) )
{
case 0:
printf("A%c", string_to_print[string_index]);
string_index++;
atomic_store_explicit( &turn, 1, memory_order_release );
break;
case 1:
continue;
default:
pthread_exit( NULL );
}
}
}
void *funcB(void *arg)
{
while (1)
{
switch ( atomic_load_explicit( &turn, memory_order_acquire ) )
{
case 0:
continue;
case 1:
printf("B%c", string_to_print[string_index]);
string_index++;
if(string_index > 9)
{
string_index = 0;
printf("\n");
}
atomic_store_explicit( &turn, 0, memory_order_release );
break;
default:
pthread_exit( NULL );
}
}
}
int main(void)
{
pthread_t A,B;
//create thread A
if (pthread_create(&A, NULL, &funcA, NULL) != 0)
{
printf("Error creating thread");
exit(-1);
}
//create thread B
if (pthread_create(&B, NULL, &funcB, NULL) != 0)
{
printf("Error creating thread");
exit(-1);
}
sleep(20);
turn = 2;
pthread_join(B, NULL);
pthread_join(A, NULL);
}
This program has the same output as the other one.
funcB
to wait forfuncA
to do its thing. WhenfuncB
callspthread_cond_signal
, it immediately loops and callspthread_mutex_lock
. It takes time for the system to send the signal to the other thread and for that thread to wake up. Before that happens,funcB
has taken the lock again, and thenfuncA
cannot get the lock and goes back to sleep, waiting. You need both threads to signal the other and to wait for a signal from the other. – Eric Postpischil Commented yesterday