Race condition

Created:

Updated:

A race condition occurs when two or more threads access a shared resource(such a file or variable) at the same time. In this case, each thread races to read and to write shared resource simultaneously. When each thread read variable at the same time, those threads will read the same value and perform their operations on the value. And when those threads write the computed value to the shared variable, the last value written overwrites the previously written value. The race condition may or may not occur depending on the timing of each thread access the shared variable, so you should be careful.

example code

C code

Here is example code to increment the value of count variable by 1. If 2 threads call the inc() function, the value of count can be 2 or 1. In C-language code, it is a single statement that increases the value by 1.

1
2
3
4
5
static count = 0;
void inc()
{
	count++;
}

Assembly code(arm aarch32 code for example)

But in Assembly code, it consists of several statements that read, increment, and store the value. So, if 2 threads call the inc() function at the same time, when reading count value, 2 thread may read 0, increase 1 each, and store 1 each, so 1 may be stored. Or even if a thread is executed first, several statements(read, increment, and store count values) may not work atomically, so there is no guarantee that the final value is 2. For example, an interrupt occurs during execution of those statements.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
...
inc:
	@ Function supports interworking.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 1, uses_anonymous_args = 0
	@ link register save eliminated.
	str	fp, [sp, #-4]!
	add	fp, sp, #0


	ldr	r3, .L2                @ load count's address
	ldr	r3, [r3]               @ load value of count to r3
	add	r3, r3, #1             @ increment r3 by 1
	ldr	r2, .L2                @ load count's address
	str	r3, [r2]               @ store r3 to count's address


	nop
	add	sp, fp, #0
	@ sp needed
	ldr	fp, [sp], #4
	bx	lr
.L3:
	.align	2
.L2:
	.word	count

race condition example in C, and how to avoid race condition by using mutex

pthread.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>

#define THREAD_COUNT (2)
static int count = 0;

static void *thread_function(void *arg)
{
	int i = 0;
	char *thread_name = arg;
	for (i = 0; i < 1000000; i++) {
		count++;
		printf("%s i = %d, count = %d\n", thread_name, i, count);
	}
	sleep(2);
	printf("\n");
	printf("%s i = %d, count = %d\n", thread_name, i, count);

	pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
	pthread_t thread_id[THREAD_COUNT];
	char thread_name[THREAD_COUNT][20];
	int ret = 0;
	char buf[20] = {0,};

	for (int i = 0; i < THREAD_COUNT; i++) {
		snprintf(buf, 9, "thread_%d", i);
		memcpy(thread_name[i], buf, sizeof(buf));
	}

	for (int i = 0; i < THREAD_COUNT; i++) {
		ret = pthread_create(&thread_id[i], NULL, &thread_function, thread_name[i]);
		if (ret != 0) {
			printf("pthread_create thread_id[%d] failed!!!\n", i);
		}
	}

	for (int i = 0; i < THREAD_COUNT; i++) {
		ret = pthread_join(thread_id[i], NULL);
		if (ret != 0) {
			printf("pthread_join thread_id[%d] failed!!!\n", i);
		}
	}

	return 0;
}

output

1
2
3
4
5
6
7
...
thread_1 i = 999998, count = 1999987
thread_1 i = 999999, count = 1999988

thread_0 i = 1000000, count = 1999988

thread_1 i = 1000000, count = 1999988

In above example, there are 2 threads that call thread_function(), and thread_function() has a for loop that executes count++ 1000000 times. Therefor, the final value of count should be 2000000, but the actual result is 1999988, which is less than 2000000.

pthread_mutex.c

In this example code, pthread_mutex_lock() and pthread_mutex_unlock() are used to avoid race condition.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <pthread.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>

#define THREAD_COUNT (2)
static int count = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

static void *thread_function(void *arg)
{
	int i = 0;
	char *thread_name = arg;
	for (i = 0; i < 1000000; i++) {
		pthread_mutex_lock(&mutex);
		count++;
		pthread_mutex_unlock(&mutex);
		printf("%s i = %d, count = %d\n", thread_name, i, count);
	}
	sleep(2);
	printf("\n");
	printf("%s i = %d, count = %d\n", thread_name, i, count);

	pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
	pthread_t thread_id[THREAD_COUNT];
	char thread_name[THREAD_COUNT][20];
	int ret = 0;
	char buf[20] = {0,};

	for (int i = 0; i < THREAD_COUNT; i++) {
		snprintf(buf, 9, "thread_%d", i);
		memcpy(thread_name[i], buf, sizeof(buf));
	}

	for (int i = 0; i < THREAD_COUNT; i++) {
		ret = pthread_create(&thread_id[i], NULL, &thread_function, thread_name[i]);
		if (ret != 0) {
			printf("pthread_create thread_id[%d] failed!!!\n", i);
		}
	}

	for (int i = 0; i < THREAD_COUNT; i++) {
		ret = pthread_join(thread_id[i], NULL);
		if (ret != 0) {
			printf("pthread_join thread_id[%d] failed!!!\n", i);
		}
	}

	return 0;
}

output

1
2
3
4
5
6
7
...
thread_0 i = 999998, count = 1999999
thread_0 i = 999999, count = 2000000

thread_1 i = 1000000, count = 2000000

thread_0 i = 1000000, count = 2000000

In this example, the value of count is 2000000, as expected.

race condition example in C++, and how to avoid race condition by using mutex

std_thread.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <thread>
#include <iostream>
#include <cstring>
#include <unistd.h>
using namespace std;
#define THREAD_COUNT (2)
static int count = 0;

static void thread_function(char *arg)
{
	int i = 0;
	char *thread_name = arg;
	for (i = 0; i < 1000000; i++) {
		count++;
		cout<<thread_name<<" i = " <<i<<", count = "<<count<<endl;
	}
	sleep(2);
	cout<<endl;
	cout<<thread_name<<" i = " <<i<<", count = "<<count<<endl;
}

int main(int argc, char *argv[])
{
	std::thread thread_id[THREAD_COUNT];
	char thread_name[THREAD_COUNT][20];
	char buf[20] = {0,};

	for (int i = 0; i < THREAD_COUNT; i++) {
		snprintf(buf, 9, "thread_%d", i);
		memcpy(thread_name[i], buf, sizeof(buf));
	}

	for (int i = 0; i < THREAD_COUNT; i++) {
		thread_id[i] = std::thread(thread_function, thread_name[i]);
	}

	for (int i = 0; i < THREAD_COUNT; i++) {
		thread_id[i].join();
	}

	return 0;
}

output

1
2
3
4
5
6
7
...
thread_1 i = 999998, count = 1999969
thread_1 i = 999999, count = 1999970

thread_0 i = 1000000, count = 1999970

thread_1 i = 1000000, count = 1999970

In above example, there are 2 threads that call thread_function(), and thread_function() has a for loop that executes count++ 1000000 times. Therefor, the final value of count should be 2000000, but the actual result is 1999970, which is less than 2000000.

std_thread_mutex.cpp

In this example code, std::mutex is used to avoid race condition.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <thread>
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <mutex>
using namespace std;
#define THREAD_COUNT (2)
static int count = 0;
std::mutex mtx;

static void thread_function(char *arg)
{
	int i = 0;
	char *thread_name = arg;
	for (i = 0; i < 1000000; i++) {
		mtx.lock();
		count++;
		cout<<thread_name<<" i = " <<i<<", count = "<<count<<endl;
		mtx.unlock();
	}
	sleep(2);
	cout<<endl;
	cout<<thread_name<<" i = " <<i<<", count = "<<count<<endl;
}

int main(int argc, char *argv[])
{
	std::thread thread_id[THREAD_COUNT];
	char thread_name[THREAD_COUNT][20];
	char buf[20] = {0,};

	for (int i = 0; i < THREAD_COUNT; i++) {
		snprintf(buf, 9, "thread_%d", i);
		memcpy(thread_name[i], buf, sizeof(buf));
	}

	for (int i = 0; i < THREAD_COUNT; i++) {
		thread_id[i] = std::thread(thread_function, thread_name[i]);
	}

	for (int i = 0; i < THREAD_COUNT; i++) {
		thread_id[i].join();
	}

	return 0;
}

output

1
2
3
4
5
6
7
...
thread_0 i = 999998, count = 1999999
thread_0 i = 999999, count = 2000000

thread_1 i = 1000000, count = 2000000

thread_0 i = 1000000, count = 2000000

In this example, the value of count is 2000000, as expected.

using std::lock_guard

In c++11, when we use std:mutex, we should call lock() when entering the shared resources protection area and unlock() when exiting the shared resources protection area. Meanwhile, there is another class which we can use mutex. std:lock_guard is a mutex wrapper, when a lock_guard object is created, it locks the the mutex, and when it leaves the csope which the lock_guard object was created, the lock_guard is destructed and the mutex is unlocked.

ex) using std::lock_guard

1
2
3
4
5
6
7
8
9
10
11
12
13
static void thread_function(char *arg)
{
	int i = 0;
	char *thread_name = arg;
	for (i = 0; i < 1000000; i++) {
		std::lock_guard<std::mutex> lg(mtx);
		count++;
		cout<<thread_name<<" i = " <<i<<", count = "<<count<<endl;
	}
	sleep(2);
	cout<<endl;
	cout<<thread_name<<" i = " <<i<<", count = "<<count<<endl;
}

Leave a comment