Чтение онлайн

на главную - закладки

Жанры

Программирование для Linux. Профессиональный подход

Самьюэл Алекс

Шрифт:

Чтобы исключить возможность гонки, необходимо сделать операции атомарными. Атомарная операция неделима и непрерывна; если она началась, то уже не может быть приостановлена или прервана, пока, наконец, не завершится. Выполнение других операций в это время становится невозможным. В нашем конкретном примере проверка переменной

job_queue
и удаление задания должны выполняться как одна атомарная операция.

4.4.2. Исключающие семафоры

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

Реализация такого решения требует поддержки от операционной системы. В Linux имеется специальное средство, называемое исключающим семафором, или мьютексом (MUTual EXclusion — взаимное исключение). Это специальная блокировка, которую в конкретный момент времени может устанавливать только одни поток. Если исключающий семафор захвачен каким-то потоком, другой поток, обращающийся к семафору, оказывается заблокированным или переведенным в режим ожидания. Как только семафор освобождается, поток продолжает свое выполнение. ОС Linux гарантирует, что между потоками, пытающимися захватить исключающий семафор, не возникнет гонка. Такой семафор может принадлежать только одному потоку, а все остальные потоки блокируются.

Чтобы создать исключающий семафор, нужно объявить переменную типа

pthread_mutex_t
и передать указатель на нее функции
pthread_mutex_init
. Вторым аргументом этой функции является указатель на объект атрибутов семафора. Как и в случае функции
pthread_create
, если объект атрибутов пуст, используются атрибуты по умолчанию. Переменная исключающего семафора инициализируется только один раз. Вот как это делается:

pthread_mutex_t mutex;

pthread_mutex_init(&mutex, NULL);

Более простой способ создания исключающего семафора со стандартными атрибутами — присвоение переменной специального значения

PTHREAD_MUTEX_INITIALIZER
. Вызывать функцию
pthread_mutex_init
в таком случае не требуется. Это особенно удобно для глобальных переменных (а в C++ — статических переменных класса). Предыдущий фрагмент программы эквивалентен следующей записи:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

Поток может попытаться захватить исключающий семафор, вызвав функцию

pthread_mutex_lock
. Если семафор свободен, он переходит во владение данного потока и функция немедленно завершается. Если же семафор уже был захвачен другим потоком. выполнение функции
pthread_mutex_lock
блокируется и возобновляется только тогда, когда семафор вновь становится свободным. Сразу несколько потоков могут ожидать освобождения исключающего семафора. Когда это событие наступает, только один поток (выбираемый произвольным образом) разблокируется и получает возможность захватить семафор; остальные потоки остаются заблокированными.

Функция

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

В листинге 4.11 представлена другая версия программы, работающей с очередью заданий. Теперь очередь "защищена" исключающим семафором. Прежде чем получить доступ к очереди (для чтения или записи), каждый поток сначала захватывает семафор. Только когда вся последовательность операций проверки очереди и удаления задания из нее будет закончена, произойдет освобождение семафора. Благодаря этому не возникает описанное выше состояние гонки.

Листинг 4.11. (job-queue2.c) Работа с очередью заданий, защищенной исключающим семафором

#include <malloc.h>

#include <pthread.h>

struct job {

 /* Ссылка на следующий элемент связанного списка. */

 struct job* next;

 /* Другие поля, описывающие требуемую операцию... */

};

/* Список отложенных заданий. */

struct job* job_queue;

/* Исключающий семафор, защищающий очередь. */

pthread_mutex_t job_queue_mutex = PTHREAD_MUTEX_INITIALIZER;

/* Обработка заданий до тех пор, пока очередь не опустеет. */

void* thread_function(void* arg) {

 while (1) {

struct job* next_job;

/* Захват семафора, защищающего очередь. */

pthread_mutex_lock(&job_queue_mutex);

/* Теперь можно проверить, является ли очередь пустой. */

if (job_queue == NULL)

next_job = NULL;

else {

/* Запрашиваем следующее задание. */

next_job = job_queue;

/* Удаляем задание из списка. */

job_queue = job_queue->next;

}

/* Освобождаем семафор, так как работа с очередью окончена. */

pthread_mutex_unlock(&job_queue_mutex);

/* Если очередь пуста, завершаем поток. */

if (next_job == NULL)

break;

/* Выполняем задание. */

process_job(next_job);

/* Очистка. */

free(next_job);

 }

 return NULL;

}

Все операции доступа к совместно используемому указателю

job_queue
происходят между вызовами функций
pthread_mutex_lock
и
pthread_mutex_unlock
. Объект задания, ссылка на который хранится в переменной
next_job
, обрабатывается только после того, как ссылка на него удаляется из очереди, что позволяет обезопасить этот объект от других потоков.

Обратите внимание на то, что, если очередь пуста (т.е. указатель

job_queue
равен
NULL
), цикл не завершается немедленно. Это привело бы к тому, что исключающий семафор так и остался бы в захваченном состоянии и не позволил бы ни одному другому потоку получить доступ к очереди заданий. Мы действуем иначе: записываем в переменную
next_job
значение
NULL
и выходим из цикла только после освобождения семафора.

Исключающий семафор блокирует доступ к участку программы, а не к переменной. В обязанности программиста входит написать код для захвата семафора перед доступом к переменной и последующего его освобождения. Вот как. например, может выглядеть функция, добавляющая новое задание к очереди:

Поделиться:
Популярные книги

Наследник с Меткой Охотника

Тарс Элиан
1. Десять Принцев Российской Империи
Фантастика:
попаданцы
альтернативная история
аниме
5.00
рейтинг книги
Наследник с Меткой Охотника

Мажор. Дилогия.

Соколов Вячеслав Иванович
Фантастика:
боевая фантастика
8.05
рейтинг книги
Мажор. Дилогия.

Железный Воин Империи

Зот Бакалавр
1. Железный Воин Империи
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Железный Воин Империи

Слово мастера

Лисина Александра
11. Гибрид
Фантастика:
попаданцы
аниме
5.00
рейтинг книги
Слово мастера

Кодекс Охотника. Книга VI

Винокуров Юрий
6. Кодекс Охотника
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Кодекс Охотника. Книга VI

Агенты ВКС

Вайс Александр
3. Фронтир
Фантастика:
боевая фантастика
космическая фантастика
5.00
рейтинг книги
Агенты ВКС

Отморозок 3

Поповский Андрей Владимирович
3. Отморозок
Фантастика:
попаданцы
5.00
рейтинг книги
Отморозок 3

Петля, Кадетский Корпус. Книга четвертая

Алексеев Евгений Артемович
4. Петля
Фантастика:
боевая фантастика
попаданцы
аниме
5.00
рейтинг книги
Петля, Кадетский Корпус. Книга четвертая

Вперед в прошлое!

Ратманов Денис
1. Вперед в прошлое
Фантастика:
попаданцы
5.00
рейтинг книги
Вперед в прошлое!

Кодекс Крови. Книга ХVI

Борзых М.
16. РОС: Кодекс Крови
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Кодекс Крови. Книга ХVI

Кодекс Охотника. Книга X

Винокуров Юрий
10. Кодекс Охотника
Фантастика:
фэнтези
попаданцы
аниме
6.25
рейтинг книги
Кодекс Охотника. Книга X

Метатель

Тарасов Ник
1. Метатель
Фантастика:
боевая фантастика
попаданцы
рпг
фэнтези
фантастика: прочее
постапокалипсис
5.00
рейтинг книги
Метатель

Камень. Книга вторая

Минин Станислав
2. Камень
Фантастика:
фэнтези
8.52
рейтинг книги
Камень. Книга вторая

Кодекс Охотника. Книга XIII

Винокуров Юрий
13. Кодекс Охотника
Фантастика:
боевая фантастика
попаданцы
аниме
7.50
рейтинг книги
Кодекс Охотника. Книга XIII