Марк Митчелл - Программирование для Linux. Профессиональный подход
- Название:Программирование для Linux. Профессиональный подход
- Автор:
- Жанр:
- Издательство:Вильямс
- Год:2002
- Город:Москва
- ISBN:5-8459-0243-6
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Марк Митчелл - Программирование для Linux. Профессиональный подход краткое содержание
Данная книга в основном посвящена программированию в среде GNU/Linux. Авторы применяют обучающий подход, последовательно излагая самые важные концепции и методики использования расширенных возможностей системы GNU/Linux в прикладных программах. Читатели научатся писать программы, к интерфейсу которых привыкли пользователи Linux; освоят такие технологии, как многозадачность, многопотоковое программирование, межзадачное взаимодействие и взаимодействие с аппаратными устройствами; смогут улучшить свои программы, сделав их быстрее, надежнее и безопаснее; поймут особенности системы GNU/Linux, ее ограничения, дополнительные возможности и специфические соглашения.
Книга предназначена для программистов, уже знакомых с языком С и имеющих базовый опыт работы в GNU/Linux.
Программирование для Linux. Профессиональный подход - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
#include
/* Выделение временного буфера. */
void* allocate_buffer(size_t size) {
return malloc(size);
}
/* Удаление временного буфера. */
void deallocate_buffer(void* buffer) {
free(buffer);
}
void do_some_work() {
/* Выделение временного буфера. */
void* temp_buffer = allocate_buffer(1024);
/* Регистрация обработчика очистки для данного буфера. Этот
обработчик будет удалять буфер при завершении или отмене
потока. */
pthread_cleanup_push(deallocate_buffer, temp_buffer);
/* Выполнение других действий... */
/* Отмена регистрации обработчика. Поскольку функции передается
ненулевой аргумент, она выполняет очистку, вызывая функцию
deallocate_buffer(). */
pthread_cleanup_pop(1);
}
В данном случае функции pthread_cleanup_pop()
передается ненулевой аргумент, поэтому функция очистки deallocate_buffer()
вызывается автоматически. В данном простейшем случае можно было в качестве обработчика непосредственно использовать стандартную библиотечную функцию free()
.
4.3.2. Очистка потоковых данных в C++
Программисты, работающие на C++, привыкли к тому, что очистку за них делают деструкторы объектов. Когда объект выходит за пределы своей области видимости, либо по достижении конца блока, либо вследствие возникновения исключительной ситуации, среда выполнения C++ гарантирует вызов деструкторов для тех автоматических переменных, у которых они есть. Это удобный механизм очистки, работающий независимо от того, как осуществляется выход из конкретного программного блока.
Тем не менее, если поток вызывает функцию pthread_exit()
, среда выполнения C++ не может гарантировать вызов деструкторов для всех автоматических переменных, находящихся в стеке потока. Чтобы этого добиться, нужно вызвать функцию pthread_exit()
в рамках конструкции try/catch
, охватывающей все тело потоковой функции. При этом перехватывается специальное исключение ThreadExitException
.
Программа, приведенная в листинге 4.9, иллюстрирует данную методику. Потоковая функция сообщает о своем намерении завершить поток, генерируя исключение ThreadExitException
, а не вызывая функцию pthread_exit()
явно. Поскольку исключение перехватывается на самом верхнем уровне потоковой функции, все локальные переменные, находящиеся в стеке потока, будут удалены правильно.
#include
class ThreadExitException {
public:
/* Конструктор, принимающий аргумент RETURN_VALUE, в котором
содержится возвращаемое потоком значение. */
ThreadExitException(void* return_value) :
thread_return_value_(return_value) {
}
/* Реальное завершение потока. В программу возвращается
значение, переданное конструктору. */
void* DoThreadExit() {
pthread_exit(thread_return_value_);
}
private:
/* Значение, возвращаемое в программу при завершении потока. */
void* thread_return_value_;
};
void do_some_work() {
while (1) {
/* Здесь выполняются основные действия... */
if (should_exit_thread_immediately())
throw ThreadExitException(/* поток возвращает */NULL);
}
}
void* thread_function(void*) {
try {
do_some_work();
} catch (ThreadExitException ex) {
/* Возникла необходимость завершить поток. */
ex.DoThreadExit();
}
return NULL;
}
4.4. Синхронизация потоков и критические секции
Программирование потоков — нетривиальная задача, ведь большинство потоков выполняется одновременно. К примеру, невозможно определить, когда система предоставит доступ к процессору одному потоку, а когда — другому. Длительность этого доступа может быть как достаточно большой, так и очень короткой, в зависимости от того, как часто система переключает задания. Если в системе есть несколько процессоров, потоки могут выполняться одновременно в буквальном смысле.
Отладка потоковой программы также затруднена, ведь не всегда можно воссоздать ситуацию, приведшую к проблеме. В одном случае программа работает абсолютно правильно, а в другом — вызывает системный сбой. Нельзя заставить систему распланировать выполнение потоков так, как она сделала при предыдущем запуске программы.
Большинство ошибок, возникающих при работе с потоками, связано с тем, что потоки обращаются к одним и тем же данным. Как уже говорилось, это одно из главных достоинств потоков, оно же является их бедствием. Если один поток заполняет структуру данными в то время, когда второй поток обращается к этой же структуре, возникает хаос. Очень часто неправильно написанные потоковые программы корректно работают только в том случае, когда один поток планируется системой с более высоким приоритетом, т.е. чаще или быстрее обращается к процессору, чем другой поток. Подобного рода ошибки называются состоянием гонки : потоки преследуют друг друга в попытке изменить одни и те же данные.
4.4.1. Состояние гонки
Предположим, что в программу поступает группа запросов, которые обрабатываются несколькими одновременными потоками. Очередь запросов представлена связанным списком объектов типа struct job
.
Когда каждый поток завершает свою операцию, он обращается к очереди и проверяет, есть ли в ней еще необработанные запросы. Если указатель job_queue
не равен NULL
, поток удаляет из списка самый верхний элемент и перемещает указатель на следующий элемент. Потоковая функции, работающая с очередью заданий, представлена в листинге 4.10.
#include
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
. Увы, теперь мы имеем два потока, выполняющих одно и то же задание.
Интервал:
Закладка: