Программирование для Linux. Профессиональный подход - Марк Митчелл
Шрифт:
Интервал:
Закладка:
В листинге 4.3 приведена исправленная версия функции main() из предыдущего, неправильного примера. В данном случае функция main() не завершается, пока оба дочерних потока не выполнят свои задания и не перестанут ссылаться на переданные им структуры.
Листинг 4.3. Исправленная функция main() из файла thread-create.cint main() {
pthread_t thread1_id;
pthread_t thread2_id;
struct char_print_parms thread1_args;
struct char_print_parms thread2_args;
/* Создание нового потока, отображающего 30000
символов 'x'. */
thread1_args.character = 'x';
thread1_args.count = 30000;
pthread_create(&thread1_id, NULL, &char_print, &thread1_args);
/* Создание нового потока, отображающего
20000 символов 'o'. */
thread2_args.character = 'o';
thread2_args.count = 20000;
pthread_create(&thread2_id, NULL, &char_print, &thread2_args);
/* Убеждаемся, что завершился первый поток. */
pthread_join(thread1_id, NULL);
/* Убеждаемся, что завершился второй поток. */
pthread_join(thread2_id, NULL);
/* Теперь можно спокойно завершать работу. */
return 0;
}
Мораль сей басни такова: убедитесь, что любые данные, переданные потоку по ссылке, не удаляются (даже другим потоком) до тех пор, пока поток не завершит свою работу с ними. Это относится как к локальным переменным, удаляемым автоматически при выходе за пределы своей области видимости, так и к динамическим переменным, удаляемым с помощью функции free() (или оператора delete в C++).
4.1.3. Значения, возвращаемые потоками
Если второй аргумент функции pthread_join() не равен NULL, то в него помещается значение, возвращаемое потоком. Как и потоковый аргумент, это значение имеет тип void*. Если поток возвращает обычное число типа int, его можно свободно привести к типу void*, а затем выполнить обратное преобразование по завершении функции pthread_join().[13]
Программа, представленная в листинге 4.4, в отдельном потоке вычисляет n-е простое число и возвращает его в программу. Тем временем функция main() может продолжать свои собственные вычисления. Сразу признаемся: алгоритм последовательного деления, используемый в функции compute_prime(), весьма неэффективен. В книгах по численным методам описаны более мощные алгоритмы (например, "решето Эратосфена").
Листинг 4.4. (primes.с) Вычисление простых чисел в потоке#include <pthread.h>
#include <stdio.h>
/* Находим простое число с порядковым номером N, где N -- это
значение, на которое указывает параметр ARG. */
void* compute_prime(void* arg) {
int candidate = 2;
int n = *((int*)arg);
while (1) {
int factor;
int is_prime = 1;
/* Проверка простого числа путем последовательного деления. */
for (factor = 2; factor < candidate; ++factor)
if (candidate % factor == 0) {
is_prime = 0;
break;
}
/* Это то простое число, которое нам нужно? */
if (is_prime) {
if (--n == 0)
/* Возвращаем найденное число в программу. */
return (void*)candidate;
}
++candidate;
}
return NULL;
}
int main() {
pthread_t thread;
int which_prime = 5000;
int prime;
/* Запускаем поток, вычисляющий 5000-е простое число. */
pthread_create(&thread, NULL, &compute_prime, &which_prime);
/* Выполняем другие действия. */
/* Дожидаемся завершения потока и принимаем возвращаемое им
значение. */
pthread_join(thread, (void*)&prime);
/* Отображаем вычисленный результат. */
printf("The %dth prime number is %d.n", which_prime, prime);
return 0;
}
4.1.4. Подробнее об идентификаторах потоков
Иногда в программе возникает необходимость определить, какой поток выполняет ее в данный момент. Функция pthread_self() возвращает идентификатор потока, в котором она вызвана. Для сравнения двух разных идентификаторов предназначена функция pthread_equal().
Эти функции удобны для проверки соответствия заданного идентификатора текущему потоку. Например, поток не должен вызывать функцию pthread_join(), чтобы ждать самого себя (в подобной ситуации возвращается код ошибки EDEADLK). Избежать этой ошибки позволяет следующая проверка:
if (!pthread_equal(pthread_self(), other_thread)) pthread_join(other_thread, NULL);
4.1.5. Атрибуты потоков
Потоковые атрибуты — это механизм настройки поведения отдельных потоков. Вспомните, что функция pthread_create() принимает аргумент, являющийся указателем на объект атрибутов потока. Если этот указатель равен NULL, поток конфигурируется на основании стандартных атрибутов.
Для задания собственных атрибутов потока выполните следующие действия.
1. Создайте объект типа pthread_attr_t.
2. Вызовите функцию pthread_attr_init(), передав ей указатель на объект. Эта функция присваивает неинициализированным атрибутам стандартные значения.
3. Запишите в объект требуемые значения атрибутов.
4. Передайте указатель на объект в функцию pthread_create().
5. Вызовите функцию pthread_attr_destroy(), чтобы удалить объект из памяти. Сама переменная pthread_attr_t не удаляется; ее можно проинициализировать повторно с помощью функции pthread_attr_init().
Один и тот же объект может быть использован для запуска нескольких потоков. Нет необходимости хранить объект после того, как поток был создан.
Для большинства Linux-приложений интерес представляет один-единственный атрибут (остальные используются в приложениях реального времени): статус отсоединения потока. Поток может быть создан как ожидаемый (по умолчанию) или отсоединенный. Ожидаемый поток, подобно процессу, после своего завершения не удаляется автоматически операционной системой Linux. Код его завершения хранится где-то в системе (как у процесса-зомби), пока какой-нибудь другой поток не вызовет функцию pthread_join(), чтобы запросить это значение. Только тогда ресурсы потока считаются освобожденными. С другой стороны, отсоединенный поток, завершившись, сразу уничтожается. Другие потоки не могут вызвать по отношению к нему функцию pthread_join() или получить возвращаемое им значение.
Чтобы задать статус отсоединения потока, воспользуйтесь функцией pthread_attr_setdetachstate(). Первый ее аргумент — это указатель на объект атрибутов потока, второй — требуемый статус. Ожидаемые потоки создаются по умолчанию, поэтому в качестве второго аргумента имеет смысл указывать только значение PTHREAD_CREATE_DETACHED.
Программа, представленная в листинге 4.5, создает отсоединенный поток, устанавливая соответствующим образом атрибуты потока.
Листинг 4.5. (detached.c) Шаблон программы, создающей отсоединенный поток#include <pthread.h>
void* thread_function(void* thread_arg) {
/* Тело потоковой функции... */
}
int main() {
pthread_attr_t attr;
pthread_t thread;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&thread, &attr, &thread_function, NULL);
pthread_attr_destroy(&attr);
/* Тело основной программы... */
/* Дожидаться завершения второго потока нет необходимости. */
return 0;
}
Даже если поток был создан ожидаемым, его позднее можно сделать отсоединенным. Для этого нужно вызвать функцию pthread_detach(). Обратное преобразование невозможно.
4.2. Отмена потока
Обычно поток завершается при выходе из потоковой функции или вследствие вызова функции pthread_exit(). Но существует возможность запросить из одного потока уничтожение другого. Это называется отменой, или принудительным завершением, потока.
Чтобы отменить поток, вызовите функцию pthread_cancel(), передав ей идентификатор требуемого потока. Далее можно дождаться завершения потока. Вообще-то, это обязательно нужно делать с целью освобождения ресурсов, если только поток не является отсоединенным. Отмененный поток возвращает специальное значение PTHREAD_CANCELED.
Во многих случаях поток выполняет код, который нельзя просто взять и прервать. Например, поток может выделить какие-то ресурсы, поработать с ними, а затем удалить. Если отмена потока произойдет где-то посередине, освободить занятые ресурсы станет невозможно, вследствие чего они окажутся потерянными для системы. Чтобы учесть эту ситуацию, поток должен решить, где и когда он может быть отменен.