October 6, 2016

A Kyu tutorial - Race conditions and concurrent programming.

Enough of this LED blinking. It is time to talk about the tricky topic of race conditions and what are known as "critical regions". The idea behind this example is to create two threads (and introduce the calls for creating threads) and to have these threads step on each others toes. The next example will show how to fix the problem.

The main idea here is that we have 3 variables: "count1", "count2", and the cleverly named variable "wally". We expect at all times that wally = count1 + count2. This is initially true, since they are all initialized to zero.

When thread "racer1" increments count1, it also increments wally.
When thread "racer2" increments count2, it also increments wally.

We stir up trouble by running "racer1" on an timer (and to roll the dice more often, we run the timer at 10,000 Hz rather than the default 1000). The trouble is that both threads have a shared resource, namely "wally". And although incrementing it is a single C statement, the processor needs to read, increment, then store the variable, which are 3 operations. If an interrupt happens in the midst of these statements, we get a race and the program detects it (and once it gets 10 of them it stops).

#include "kyu.h"
#include "thread.h"

static volatile int wally = 0;
static volatile int count1 = 0;
static volatile int count2 = 0;

void
racer1 ( int xx )
{
	for ( ;; ) {
	    thr_delay ( 1 );
	    count1++;
	    wally++;
	}
}

void
racer2 ( int xx )
{
	int races = 0;

	while ( races < 10 ) {

	    wally++;
	    count2++;
	    if ( wally != count1 + count2 ) {
		printf ( "Race after %d / %d (%d)\n", count1, count2, wally );
		wally = count1 + count2;
		races++;
	    }
	}
	printf ( "Done\n" );
}

void
user_init ( int xx )
{
	timer_rate_set ( 10000 );
	(void) thr_new ( "racer1", racer1, (void *) 0, 20, 0 );
	(void) thr_new ( "racer2", racer2, (void *) 0, 30, 0 );

	thr_show ();
}
Note the calls to thr_new(). The arguments are as follows: The thread priority is an important issue. Lower numbers are "more urgent" (so here "racer1" is more urgent). Most of the time though, "racer1" is blocked doing the thread delay (of 100 microseconds), and during that time, the lower priority "racer2" is running non-stop.

In a real time system like Kyu, the scheduler is emphatically unfair. At any time, the most urgent ready thread runs, period, no questions asked. This is a bit of a shock to some people, but in a properly designed real time system, this is exactly as it should be. If the most urgent thread never blocks, that would probably be a design failure and indeed all other threads would get starved. More on this later.


Have any comments? Questions? Drop me a line!

Kyu / tom@mmto.org