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