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

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

Жанры

Программирование для 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
и выходим из цикла только после освобождения семафора.

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

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

Дракон - не подарок

Суббота Светлана
2. Королевская академия Драко
Фантастика:
фэнтези
6.74
рейтинг книги
Дракон - не подарок

Бастард Императора. Том 8

Орлов Андрей Юрьевич
8. Бастард Императора
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Бастард Императора. Том 8

Чужая дочь

Зика Натаэль
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Чужая дочь

Эра Мангуста. Том 2

Третьяков Андрей
2. Рос: Мангуст
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Эра Мангуста. Том 2

Идеальный мир для Лекаря 14

Сапфир Олег
14. Лекарь
Фантастика:
юмористическое фэнтези
попаданцы
аниме
5.00
рейтинг книги
Идеальный мир для Лекаря 14

Один на миллион. Трилогия

Земляной Андрей Борисович
Один на миллион
Фантастика:
боевая фантастика
8.95
рейтинг книги
Один на миллион. Трилогия

Помещицы из будущего

Порохня Анна
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Помещицы из будущего

Шлейф сандала

Лерн Анна
Фантастика:
фэнтези
6.00
рейтинг книги
Шлейф сандала

Черный маг императора 2

Герда Александр
2. Черный маг императора
Фантастика:
юмористическая фантастика
попаданцы
аниме
6.00
рейтинг книги
Черный маг императора 2

Император

Рави Ивар
7. Прометей
Фантастика:
фэнтези
7.11
рейтинг книги
Император

Бандит 2

Щепетнов Евгений Владимирович
2. Петр Синельников
Фантастика:
боевая фантастика
5.73
рейтинг книги
Бандит 2

На границе империй. Том 9. Часть 2

INDIGO
15. Фортуна дама переменчивая
Фантастика:
космическая фантастика
попаданцы
5.00
рейтинг книги
На границе империй. Том 9. Часть 2

Князь Серединного мира

Земляной Андрей Борисович
4. Страж
Фантастика:
фэнтези
попаданцы
5.00
рейтинг книги
Князь Серединного мира

Чайлдфри

Тоцка Тала
Любовные романы:
современные любовные романы
6.51
рейтинг книги
Чайлдфри