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

Скачивание начинается... Если скачивание не началось автоматически, пожалуйста нажмите на эту ссылку.
Жалоба
Напишите нам, и мы в срочном порядке примем меры.
Описание книги "Программирование для Linux. Профессиональный подход"
Описание и краткое содержание "Программирование для Linux. Профессиональный подход" читать бесплатно онлайн.
Данная книга в основном посвящена программированию в среде GNU/Linux. Авторы применяют обучающий подход, последовательно излагая самые важные концепции и методики использования расширенных возможностей системы GNU/Linux в прикладных программах. Читатели научатся писать программы, к интерфейсу которых привыкли пользователи Linux; освоят такие технологии, как многозадачность, многопотоковое программирование, межзадачное взаимодействие и взаимодействие с аппаратными устройствами; смогут улучшить свои программы, сделав их быстрее, надежнее и безопаснее; поймут особенности системы GNU/Linux, ее ограничения, дополнительные возможности и специфические соглашения.
Книга предназначена для программистов, уже знакомых с языком С и имеющих базовый опыт работы в GNU/Linux.
Отладка потоковой программы также затруднена, ведь не всегда можно воссоздать ситуацию, приведшую к проблеме. В одном случае программа работает абсолютно правильно, а в другом — вызывает системный сбой. Нельзя заставить систему распланировать выполнение потоков так, как она сделала при предыдущем запуске программы.
Большинство ошибок, возникающих при работе с потоками, связано с тем, что потоки обращаются к одним и тем же данным. Как уже говорилось, это одно из главных достоинств потоков, оно же является их бедствием. Если один поток заполняет структуру данными в то время, когда второй поток обращается к этой же структуре, возникает хаос. Очень часто неправильно написанные потоковые программы корректно работают только в том случае, когда один поток планируется системой с более высоким приоритетом, т.е. чаще или быстрее обращается к процессору, чем другой поток. Подобного рода ошибки называются состоянием гонки: потоки преследуют друг друга в попытке изменить одни и те же данные.
4.4.1. Состояние гонки
Предположим, что в программу поступает группа запросов, которые обрабатываются несколькими одновременными потоками. Очередь запросов представлена связанным списком объектов типа struct job.
Когда каждый поток завершает свою операцию, он обращается к очереди и проверяет, есть ли в ней еще необработанные запросы. Если указатель job_queue не равен NULL, поток удаляет из списка самый верхний элемент и перемещает указатель на следующий элемент. Потоковая функции, работающая с очередью заданий, представлена в листинге 4.10.
Листинг 4.10. (job-queue1.c) Потоковая функция, работающая с очередью заданий#include <malloc.h>
struct job {
/* Ссылка на следующий элемент связанного списка. */
struct job* next;
/* Другие поля, описывающие требуемую операцию... */
};
/* Список отложенных заданий. */
struct job* job_queue;
/* Обработка заданий до тех пор, пока очередь не опустеет. */
void* thread_function(void* arg) {
while (job_queue != NULL) {
/* Запрашиваем следующее задание. */
struct job* next_job = job_queue;
/* Удаляем задание из списка. */
job_queue = job_queue->next;
/* выполняем задание. */
process_job(next_job);
/* Очистка. */
free(next_job);
}
return NULL;
}
Теперь предположим, что два потока завершают свои операции примерно в одно и то же время, а в очереди остается только одно задание. Первый поток проверяет, равен ли указатель job_queue значению NULL, и, обнаружив, что очередь не пуста, входит в цикл, где сохраняет указатель на объект задания в переменной next_job. В этот момент Linux прерывает первый поток и активизирует второй. Он тоже проверяет указатель job_queue, устанавливает, что он не равен NULL, и записывает тот же самый указатель в свою переменную next_job. Увы, теперь мы имеем два потока, выполняющих одно и то же задание.
Далее ситуация только ухудшается. Первый поток удаляет последнее задание из очереди. делая переменную 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 представлена другая версия программы, работающей с очередью заданий. Теперь очередь "защищена" исключающим семафором. Прежде чем получить доступ к очереди (для чтения или записи), каждый поток сначала захватывает семафор. Только когда вся последовательность операций проверки очереди и удаления задания из нее будет закончена, произойдет освобождение семафора. Благодаря этому не возникает описанное выше состояние гонки.
Листинг 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;
/* Выполняем задание. */
Подписывайтесь на наши страницы в социальных сетях.
Будьте в курсе последних книжных новинок, комментируйте, обсуждайте. Мы ждём Вас!
Похожие книги на "Программирование для Linux. Профессиональный подход"
Книги похожие на "Программирование для Linux. Профессиональный подход" читать онлайн или скачать бесплатно полные версии.
Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.
Отзывы о "Марк Митчелл - Программирование для Linux. Профессиональный подход"
Отзывы читателей о книге "Программирование для Linux. Профессиональный подход", комментарии и мнения людей о произведении.