I have two threads
xThread
: Continuously Prints X on the console
inputThread
: Gets input from the stdin
The continuous printing stops when the user enters 'C' or 'c'
#include<stdio.h>
#include<sys/select.h>
#include<pthread.h>
#define S sleep(0)
int read_c = 0;
pthread_mutex_t read_c_mutex = PTHREAD_MUTEX_INITIALIZER;
void* inputThread_fn(void* arg)
{
char inputChar;
while(1)
{
S;
printf("\nChecking input");
scanf("%c",&inputChar);
if(inputChar=='C' || inputChar == 'c')
{
pthread_mutex_trylock(&read_c_mutex); /*<--This must be _lock ?
because with the use of trylock even If i don't aquire a lock I go ahead and modify
the variable?*/
read_c = 1;
pthread_mutex_unlock(&read_c_mutex);
pthread_exit(NULL);
}
}
}
void* xThread_fn(void* arg)
{
while(1)
{
S;
pthread_mutex_trylock(&read_c_mutex);
if(!read_c)
printf(" X");
else
pthread_exit(NULL);
pthread_mutex_unlock(&read_c_mutex);
}
}
void* yThread_fn(void* arg)
{
while(1)
{
S;
pthread_mutex_trylock(&read_c_mutex);
if(!read_c)
printf(" Y");
else
pthread_exit(NULL);
pthread_mutex_unlock(&read_c_mutex);
}
}
int main()
{
pthread_t xThread,yThread,inputThread;
pthread_create(&xThread,NULL,xThread_fn,NULL);
pthread_create(&inputThread,NULL,inputThread_fn,NULL);
pthread_join(xThread,NULL);
pthread_join(inputThread,NULL);
return 0;
}
When I use sleep(1)
the threads are spawned and [irrespective of which thread is started first] when the program reaches scanf
in inputThread
it halts for the user input and the code does not proceed until I enter an input.
When I execute the code with sleep(0)
, scanf
does not halt for the input, it keeps printing 'X' until I enter 'C' or 'c'
Does sleep()
interfere with scanf
in someway?
Note: I am aware of select
being used for non-blocking input. I have tried the same too and the code runs fine. I just want to know in the above case why inconsistent behaviour arises?
Update (Using trylock
)
void* inputThread_fn(void* arg)
{
char inputChar;
while(1)
{
S;
scanf("%c",&inputChar);
if(inputChar=='C' || inputChar == 'c')
{
pthread_mutex_trylock(&read_c_mutex);
read_c = 1;
pthread_mutex_unlock(&read_c_mutex);
pthread_exit(NULL);
}
}
}
void* xThread_fn(void* arg)
{
while(1)
{
S;
pthread_mutex_trylock(&read_c_mutex);
if(!read_c)
{
pthread_mutex_unlock(&read_c_mutex);
printf(" X");
}
else
{
pthread_mutex_unlock(&read_c_mutex);
pthread_exit(NULL);
}
fflush(stdout);
}
}
void* yThread_fn(void* arg)
{
while(1)
{
S;
pthread_mutex_trylock(&read_c_mutex);
if(!read_c)
{
pthread_mutex_unlock(&read_c_mutex);
printf(" Z");
fflush(stdout);
}
else
{
pthread_mutex_unlock(&read_c_mutex);
pthread_exit(NULL);
}
}
}
The reason you don't see output is because you're not flushing the buffer.
The reason you don't need to flush the buffer with sleep(0)
is because the writer thread writes so much data that the buffer fills up and is automatically flushed.
#define SLEEP_TIME 1
void* xThread_fn(void* arg)
{
while (1) {
sleep(SLEEP_TIME);
pthread_mutex_lock(&read_c_mutex);
if (read_c) {
pthread_mutex_unlock(&read_c_mutex);
return NULL;
}
pthread_mutex_unlock(&read_c_mutex);
printf(" X");
fflush(stdout); // <-- necessary
}
}
Don't use pthread_mutex_trylock()
Don't use pthread_mutex_trylock()
here. It's wrong.
The difference between lock()
and trylock()
is that lock()
will always succeed1 but trylock()
will sometimes fail. That's why it's called "try".
Since trylock()
sometimes fails, you have to handle the case where it failed. Your code doesn't handle the case: it simply plows forward, pretending it acquired the lock. So, suppose trylock()
doesn't lock the mutex. What happens?
pthread_mutex_trylock(&read_c_mutex); // Might fail (i.e., not lock the mutex)
read_c = 1; // Modifying shared state (Wrong!)
pthread_mutex_unlock(&read_c_mutex); // Unlocking a mutex (Wrong!)
Then there's the question of how the code should handle trylock()
failing. If you can't answer this question, then the default answer is "use lock()
".
In the reader thread, you can't use trylock()
because you have to lock the mutex:
int r = pthread_mutex_trylock(&read_c_mutex);
if (r != 0) {
// Uh... what are we supposed to do here? Try again?
} else {
read_c = 1;
pthread_mutex_unlock(&read_c_mutex);
}
In the writer thread, there's no point in using trylock()
:
int r = pthread_mutex_trylock(&read_c_mutex);
if (r != 0) {
// Okay, just try again next loop...
} else {
if (read_c) {
pthread_mutex_unlock(&read_c_mutex);
pthread_exit(NULL);
} else {
pthread_mutex_unlock(&read_c_mutex);
}
}
However, this is entirely pointless. The only reason trylock()
will fail in the writer thread is if the reader thread owns the lock, which only happens if it is currently in the process of setting read_c = 1;
. So you might as well wait for it to finish, since you know you're going to exit anyway (why write more output after you know that the user has signaled your program to stop?)
Just use lock()
. You'll use lock()
99% of the time, and trylock()
is for the other 1%.
1: The lock()
function can fail, but this usually means you've misused the mutex.
Misconceptions about lock()
and trylock()
You said this about trylock()
:
If i have another thread accessing the variable read_input
then will it be appropriate to use it?
I think there is a very fundamental misunderstanding here about the nature of mutexes. If another thread weren't accessing the variable at the same time, then you wouldn't need a mutex at all.
Suppose you're doing important work at the office, and you need to use the photocopier. Only one person can use the photocopier at a time. You go to the photocopier and someone's already using it.
If you wait in line until it's your turn, then that's lock()
.
If you give up and go back to your desk, then that's trylock()
. (Your program actually ignores the return code for trylock()
, so you basically start mashing buttons on the photocopier even if someone else is using it.)
Now imagine that it takes one minute to use the photocopier, only two people ever use the photocopier, and they only use the photocopier once every twenty years.
If you use lock()
, then you wait in line for at most one minute before using the photocopier.
If you use trylock()
, then you give up and go back to your desk and wait twenty years before trying the photocopier again.
It doesn't make any sense to use trylock()
, does it? Are your threads so impatient that they can't spend even one minute in line once every twenty years?
Now your boss comes down and said, "Where is that report I asked you to photocopy?" And you say, "Well, I went to the photocopier six years ago but someone was using it."
The numbers (one minute every twenty years) are based on Latency Numbers Every Programmer Should Know, where it notes that locking/unlocking a mutex is about 25ns. So if we pretend that it takes one minute to lock and then unlock a mutex, then sleep(1)
causes the thread to wait for twenty years.