2

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.

Christian.K
  • 47,778
  • 10
  • 99
  • 143
Annamalai
  • 25
  • 2
  • 5
  • The thing about race conditions is, that they are not really predictable. Then, how many processors do you have? – Christian.K Mar 08 '12 at 06:57
  • I have only one core, /proc/cpuinfo processor : 0 vendor_id : GenuineIntel cpu family : 15 model : 6 model name : Intel(R) Pentium(R) D CPU 2.80GHz stepping : 4 cpu MHz : 2793.070 cache size : 2048 KB fdiv_bug : no hlt_bug : no f00f_bug : no coma_bug : no fpu : yes fpu_exception : yes cpuid level : 6 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss nx constant_tsc up pni bogomips : 5586.14 – Annamalai Mar 08 '12 at 07:09
  • uname -a gives the following output, Linux grao.linux 2.6.18-238.19.1.el5 #1 SMP Fri Jul 15 07:32:29 EDT 2011 i686 i686 i386 GNU/Linux. I am running CentOS 5 – Annamalai Mar 08 '12 at 07:12

3 Answers3

11

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.

Dietrich Epp
  • 205,541
  • 37
  • 345
  • 415
  • You mean to say that, context switching happened between loading the value to register (to be read during print) and actual print opreation? By the way, I wanted to demostrate race condition, so I should bot use mutex. Anyways, thanks for that part as well, pls repl – Annamalai Mar 08 '12 at 07:33
  • The `printf` operation takes a long time as far as the processor is concerned, so it is likely that the thread is getting interrupted in the middle of the `printf` call. The glibc `printf` uses a mutex to make sure that two do not execute at the same time, but there are still plenty of opportunities to interrupt the thread while the lock is not held. – Dietrich Epp Mar 08 '12 at 07:40
  • Actually I think the `printf` is the real problem here. They are just printed out of order. – Jens Gustedt Mar 08 '12 at 08:38
  • @JensGustedt: On glibc, `printf` acquires a lock. So it will not be a problem. – Dietrich Epp Mar 08 '12 at 08:46
  • @Dietrich, exactly there is the problem. The prints come out of order, because the access to stdin is mutexed. From the list you see in the output of the given program, you only see in which order the access to stdin was given, not in which order `c` was incremented, and also you don't see which thread is printing what value. – Jens Gustedt Mar 08 '12 at 09:37
  • The problem would only be worse if there were no mutex on stdin. – Dietrich Epp Mar 08 '12 at 10:28
1

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.

Community
  • 1
  • 1
Mike Mertsock
  • 11,825
  • 7
  • 42
  • 75
  • The suggestion to use `volatile` is wrong, and it won't help in this case. The value of `c` will ALWAYS have to be loaded into local storage. – Dietrich Epp Mar 08 '12 at 07:18
  • I declared c as a vloatile variable. It doeas not make a difference. – Annamalai Mar 08 '12 at 07:18
  • Dietrich Epp, I am not using any local variable (in the function's stack). That being the case, how can it be stored locally? – Annamalai Mar 08 '12 at 07:20
  • Function arguments must be evaluated and stored locally before a function call can be executed. I have added an answer. – Dietrich Epp Mar 08 '12 at 07:27
0

I think your are completely on the wrong track. Probably most of the time you are not racing on the access to c but on your access to stdout. On modern OS access to the stdio functions is mutexed. If there are several threads hammering on a stream like your example, there is a high probability that they are served out of order. This is the phenomenon that you are observing.

Measuring or accounting for real race conditions is much more difficult than you seem to imagine. One way would for example be to keep track of all values that have been encountered in an array or so.

At minimum you'd have to prefix each output with something like a thread ID to know where this output is coming from. Try something like

void *fnC(void * arg)
{
    int id = *(int*)arg;
    for(int i=0;i<10;i++)
    {   
        c++;
        printf(" %d:%d", id, c); 
    }   
}

and create your threads with a pointer to int argument using an array such as

int ids[] = { 0, 1 };

instead of the NULL.

Jens Gustedt
  • 76,821
  • 6
  • 102
  • 177
  • With the given program, I am just expecting one missing value and one duplicated value in the range 1 to 20. The point is, instead of missing the va;ue, the threads print it out of order. As for racing to access stdio, I am just using the statement `c++` to read and write a commont (unprotected) variable, and using printf to read the same variable. – Annamalai Mar 08 '12 at 08:46
  • So, Jens, do you mean to say that, using printf (stdio) function to demostrate race condition is not right, as printf is mutexed? your comments please. – Annamalai Mar 08 '12 at 08:51
  • @Annamalai, exactly. From your list you only see when your threads had access to stdin, not when they performed the `++` operation. In addition you don't see which thread performed which increment. – Jens Gustedt Mar 08 '12 at 09:43
  • I cant understand your previuos comment. Kindly, can you explain in a little detailed manner. – Annamalai Mar 08 '12 at 10:07
  • @Annamalai, in your program you have at least two resources that are disputed between the threads: your variable `c` and `stdout` (ah, I said stdin, sorry). Printing on `stdout` is much more costly and lengthy than `++` so most of the congestion will take place there. For each of the threads you will see their respective prints appear in order, but the prints from both will be mixed in the output. The final order in which they appear on you screen does not necessarily correspond to the order in which the `printf` statements where issued. – Jens Gustedt Mar 08 '12 at 11:55