I have written the follwing code to demonstrate race condition between 2 threads of a same process.
`
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int c = 0;
void *fnC()
{
int i;
for(i=0;i<10;i++)
{
c++;
printf(" %d", c);
}
}
int main()
{
int rt1, rt2;
pthread_t t1, t2;
/* Create two threads */
if( (rt1=pthread_create( &t1, NULL, &fnC, NULL)) )
printf("Thread creation failed: %d\n", rt1);
if( (rt2=pthread_create( &t2, NULL, &fnC, NULL)) )
printf("Thread creation failed: %d\n", rt2);
/* Wait for both threads to finish */
pthread_join( t1, NULL);
pthread_join( t2, NULL);
printf ("\n");
return 0;
}
`
I ran this program, and expected a race condition to occur between the 2 threads (but, I understand that the probablity for race condition is very less, as the thread main function very small). I ran this 50000 times. The following was the output,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 - 49657 times (no race condition)
1 3 4 5 6 7 8 9 10 11 2 12 13 14 15 16 17 18 19 20 - 244 times (race condition occurs)
2 3 4 5 6 7 8 9 10 11 1 12 13 14 15 16 17 18 19 20 - 99 times (race condition occurs)
The question is, When race condition occurs as in output 2, thread 1 prints 1 and is swapped out of the processor and thread 2 comes in. It starts working and after thread 2 prints 11, it gets swapped out, thread 1 comes in. It has to print 12, but rather it is printing 2 (actually 2 should be missing). I can't figure out how. Please help me understand what happens here.
You're thinking in a C mindset, but if you want to think about race conditions you have to think on a lower level.
In a debugger, you normally set a breakpoint on a single line of code, and you can watch each line of code being executed by stepping through your program. But that's not how the machine works, the machine may execute several instructions for each line of code and threads can be interrupted anywhere.
Let's look at this one line.
printf(" %d", c);
In machine code, it looks something like this:
load pointer to " %d" string constant
load value of c global
# <- thread might get interrupted here
call printf
So the behavior is not unexpected. You have to load the value of c before you can call printf, so if the thread is interrupted there is always a chance that c is stale by the time printf does anything. Unless you do something to stop it.
Fixing the race condition:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int c = 0;
void *func(void *param)
{
int i;
for (i=0; i<10; i++) {
pthread_mutex_lock(&mutex);
c++;
printf(" %d", c);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
What does volatile do?
The code in the question can translate to assembly code like the following:
load the current value of c
add 1 to it
store it in c
call printf
It doesn't have to reload c after it increments it, since the C compiler is allowed to assume that nobody else (no other thread or device) changes memory except the current thread.
If you use volatile, the compiler will be strict about keeping every load and store operation, and the assembly will look something like this:
load the current value of c
add 1 to it
store it in c
# compiler is not allowed to cache c
load the current value of c
call printf
This doesn't help. In fact, volatile almost never helps. Most C programmers don't understand volatile, and it's almost useless for writing multithreaded code. It's useful for writing signal handlers, memory-mapped IO (device drivers / embedded programming), and it's useful for correctly using setjmp/longjmp.
Footnote:
The compiler can't cache the value of c across a call to printf, because as far as the compiler knows, printf can change c (c is a global variable, after all). Someday, the compiler may get more sophisticated and it may know that printf doesn't change c, so the program may break even more severly.
I would guess that the value of 2 is being cached in a register, so thread 1 isn't seeing the correct current value of c that was last set by the other thread. Try using the volatile keyword in the declaration of c, that might make a difference. See Why is volatile needed in C? for some good discussions of volatile.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With