Марк Митчелл - Программирование для Linux. Профессиональный подход
- Название:Программирование для Linux. Профессиональный подход
- Автор:
- Жанр:
- Издательство:Вильямс
- Год:2002
- Город:Москва
- ISBN:5-8459-0243-6
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Марк Митчелл - Программирование для Linux. Профессиональный подход краткое содержание
Данная книга в основном посвящена программированию в среде GNU/Linux. Авторы применяют обучающий подход, последовательно излагая самые важные концепции и методики использования расширенных возможностей системы GNU/Linux в прикладных программах. Читатели научатся писать программы, к интерфейсу которых привыкли пользователи Linux; освоят такие технологии, как многозадачность, многопотоковое программирование, межзадачное взаимодействие и взаимодействие с аппаратными устройствами; смогут улучшить свои программы, сделав их быстрее, надежнее и безопаснее; поймут особенности системы GNU/Linux, ее ограничения, дополнительные возможности и специфические соглашения.
Книга предназначена для программистов, уже знакомых с языком С и имеющих базовый опыт работы в GNU/Linux.
Программирование для Linux. Профессиональный подход - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Далее ситуация только ухудшается. Первый поток удаляет последнее задание из очереди. делая переменную job_queue
равной NULL
. Когда второй поток попытается выполнить операцию job_queue->next
, возникнет фатальная ошибка сегментации.
Это наглядный пример гонки за ресурсами. Если программе "повезет", система не распланирует потоки именно таким образом и ошибка не проявится. Возможно, только в сильно загруженной системе (или в новой многопроцессорной системе важного клиента!) произойдет "необъяснимый" сбой.
Чтобы исключить возможность гонки, необходимо сделать операции атомарными . Атомарная операция неделима и непрерывна; если она началась, то уже не может быть приостановлена или прервана, пока, наконец, не завершится. Выполнение других операций в это время становится невозможным. В нашем конкретном примере проверка переменной 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 представлена другая версия программы, работающей с очередью заданий. Теперь очередь "защищена" исключающим семафором. Прежде чем получить доступ к очереди (для чтения или записи), каждый поток сначала захватывает семафор. Только когда вся последовательность операций проверки очереди и удаления задания из нее будет закончена, произойдет освобождение семафора. Благодаря этому не возникает описанное выше состояние гонки.
#include
#include
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
и выходим из цикла только после освобождения семафора.
Исключающий семафор блокирует доступ к участку программы, а не к переменной. В обязанности программиста входит написать код для захвата семафора перед доступом к переменной и последующего его освобождения. Вот как. например, может выглядеть функция, добавляющая новое задание к очереди:
void enqueue_job(struct job* new_job) {
pthread_mutex_lock(&job_queue_mutex);
new_job->next = job_queue;
job_queue = new_job;
pthread_mutex_unlock(&job_queue_mutex);
}
4.4.3. Взаимоблокировки исключающих семафоров
Интервал:
Закладка: