Pthread_create(3) — linux man page

Обзор мьютексов

Рассмотрим простой пример: несколько потоков обращаются к одной общей переменной. Часть потоков
эту переменную увеличивают (plus потоки), а часть уменьшают на единицу (minus потоки). Число plus и minus
потоков равно. Таким образом, мы ожидаем, что к концу работы программы значение исходной переменной будет прежним.

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <pthread.h>

static int counter = 0;

void* minus(void *args) {
	int local;

	local = counter;
	printf("min %d\n", counter);
	local = local - 1;
	counter = local;	
	return NULL;
}

void* plus(void *args) {
	int local;

	local = counter;
	printf("pls %d\n", counter);
	local = local + 1;
	counter = local;
	return NULL;
}

#define NUM_OF_THREADS 100

int main() {
	pthread_t threads;
	size_t i;

	printf("counter = %d\n", counter);
	for (i = 0; i < NUM_OF_THREADS/2; i++) {
		pthread_create(&threads, NULL, minus, NULL);
	}
	for (; i < NUM_OF_THREADS; i++) {
		pthread_create(&threads, NULL, plus, NULL);
	}
	for (i = 0; i < NUM_OF_THREADS; i++) {
		pthread_join(threads, NULL);
	}
	printf("counter = %d", counter);
	_getch();
	return 0;
}

Если выполнить код, то он будет возвращать различные значения. Чаще всего они не будут равны нулю. Разберёмся, почему так происходит.
Рассмотрим код функций, которые выполняются в отдельных потоках

void* minus(void *args) {
	int local;

	local = counter;
	printf("min %d\n", counter);
	local = local - 1;
	counter = local;	
	return NULL;
}

Во-первых, у нас имеется локальная переменная local. Во вторых, используется тяжёлая и медленная функция printf. В тот момент, когда мы
присваиваем локальной переменной значение counter, другой поток может в то же самое время взять это значение и поменять.

Для простоты рассмотрим 4 потока – два plus и два minus

Один из возможных вариантов развития собитий при совместном доступе четырёх потоков к переменной
Действие counter plus 1 local plus 2 local minus 1 local minus 2 local
plus 1 помещает в local значение counter
plus 2 помещает в local значение counter
plus 1 инкрементирует local и выводит значение на печать 1
minus 1 помещает в local значение counter 1
plus 1 помещает в переменную counter значение local 1 1
minus 2 помещает в local значение counter 1 1 1
plus 2 инкрементирует local и выводит значение на печать 1 1 1 1
plus 2 помещает в переменную counter значение local 1 1 1 1
minus 2 декрементирует local и выводит значение на печать 1 1 1
minus 2 помещает в переменную counter значение local 1 1
minus 1 декрементирует local и выводит значение на печать 1 1 -1
minus 1 помещает в переменную counter значение local -1 1 1 -1

Это один из возможных сценариев развития событий. Очевидно, что могут быть значения от минус 2 до плюс 2. Какой из них будет выполнен, в общем случае не известно.

Проблема заключается в том, что у нас имеется несинхронизированный доступ к общему ресурсу. Мы бы хотели сделать так, чтобы на время
работы с ресурсом (всё тело функций minus и plus) к ним имел доступ только один поток, а остальные ждали, пока ресурс освободится.
Это так называемое mutual exclusion – взаимное исключение, случай, когда необходимо удостовериться в том, что два (и более…)
конкурирующих потока не находятся в критической секции кода одновременно.

В библиотеке pthreads один из методов разрешить эту ситуацию – это мьютексы. Мьютекс – это объект, который может находиться в двух состояниях. Он либо заблокирован (занят, залочен, захвачен) каким-то потоком, либо свободен.
Поток, который захватил мьютекс, работает с участком кода. Остальные потоки, когда достигают мьютекса, ждут его разблокировки. Разблокировать мьютекс может только тот поток, который его захватил. Обычно освобождение занятого мьютекса происходит после исполнения критичного к совместному доступу участка кода.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector