Нейл Мэтью - Основы программирования в Linux
int time_to_exit = 0;
Далее инициализируется мьютекс:
res = pthread_mutex_init(&work_mutex, NULL);
if (res != 0) {
perror("Mutex initialization failed");
exit(EXIT_FAILURE);
}
Затем запускается новый поток. Далее приведен код, выполняемый в функции потока:
pthread_mutex_lock(&work_mutex);
while(strncmp("end", work_area, 3) != 0) {
printf("You input id charactersn", strlen(work_area)-1);
work_area[0] = ' ';
pthread_mutex_unlock(&work_mutex);
sleep(1);
pthread_mutex_lock(&work_mutex);
while (work_area[0] == ' ') {
pthread_mutex_unlock(&work_mutex);
sleep(1);
pthread_mutex_lock(&work_mutex);
}
}
time_to_exit = 1;
work_area[0] = ' ';
pthread_mutex_unlock(&work_mutex);
Сначала новый поток пытается заблокировать мьютекс. Если он уже заблокирован, вызов задерживается до тех пор, пока мьютекс не освободится. После получения доступа вы проверяете, нет ли к вам запроса на завершение выполнения. Если запрашивается завершение, просто задайте переменную time_to_exit, сотрите первый символ в рабочей области и завершите выполнение.
Если вы не хотите завершать выполнение, сосчитайте символы и очистите первый символ, сделав его пустым (null). Пустой первый символ применяется как способ информирования считывающей программы о завершении подсчета символов. Далее вы открываете мьютекс и ждете выполнения потока main. Периодически вы пытаетесь заблокировать мьютекс и, когда вам это удается, проверяете, подготовил ли поток main новую работу для вас. Если нет, вы открываете мьютекс и ждете какое-то время. Если работа есть, вы считаете символы и выполняете проход цикла снова.
Далее приведен поток main.
pthread_mutex_lock(&work_mutex)
printf("Input some text. Enter 'end' to finishn");
while (!time_to_exit) {
fgets(work_area, WORK_SIZE, stdin);
pthread_mutex_unlock(&work_mutex);
while(1) {
pthread_mutex_lock(&work_mutex);
if (work_area[0] != ' ') {
pthread_mutex_unlock(&work_mutex);
sleep(1);
} else {
break;
}
}
}
pthread_mutex_unlock(&work_mutex);
Он аналогичен второму потоку. Вы блокируете рабочую область и можете читать в нее текст, а затем вы снимаете блокировку, чтобы открыть доступ другому потоку для подсчета слов. Периодически вы блокируете мьютекс, проверяете, сосчитаны ли слова (элемент work_area[0] равен пустому символу), и освобождаете мьютекс, если нужно продолжить ожидание. Как уже отмечалось ранее, этот вид опроса и получения ответа в основном не слишком удачный прием и в реальной жизни вам, возможно, придется применить семафор для его замены. Тем не менее, программный код справляется с задачей демонстрации примера применения мьютекса.
Атрибуты потока
Когда мы начали рассматривать потоки, то не обсуждали более сложную тему — атрибуты потока. Теперь, рассказав о синхронизации потоков — ключевой теме главы, мы можем вернуться назад и остановиться на этих характеристиках потока. Существует лишь несколько атрибутов потока, которыми вы можете управлять; здесь мы собираемся обсудить только те, которые вам понадобятся, скорее всего. Подробную информацию о других атрибутах вы можете найти в интерактивном справочном руководстве.
Во всех предыдущих примерах вы должны были повторно синхронизовать потоки с помощью функции pthread_join, прежде чем разрешить программе завершить выполнение. Это необходимо сделать, если вы хотите, чтобы один поток вернул данные другому потоку, создавшему данный. Иногда вам не нужно ни возвращать информацию из второго потока в поток main, ни заставлять поток main ждать этого.
Предположим, что вы создаете второй поток для записи в буфер резервной копии файла данных, который редактируется, пока поток main продолжает обслуживать пользователя. Когда создание копии закончено, второй поток может тут же завершиться. Ему не нужно присоединяться к потоку main.
Вы можете создать потоки, ведущие себя подобным образом. Они называются отсоединенными или обособленными потоками, и вы создаете их, изменяя атрибуты потока или вызывая функцию pthread_detach. Поскольку мы хотим продемонстрировать атрибуты, то применим здесь первый метод.
Самая важная функция, которая вам понадобится, — pthread_attr_init, инициализирующая объект атрибутов потока:
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
И снова 0 возвращается в случае успешного завершения и код ошибки в случае аварийного.
Есть и функция для уничтожения: pthread_attr_destroy. Ее задача — обеспечить чистое уничтожение объекта атрибутов. После того как объект уничтожен, он не может быть использован снова до тех пор, пока не будет инициализирован повторно.
Когда вы инициализировали объект атрибутов потока, можно использовать множество дополнительных функций, с помощью которых задается поведение разных атрибутов. Далее перечислены основные из них (полный список вы можете найти в интерактивном справочном руководстве, в разделе, посвященном pthread.h), но мы рассмотрим подробно только два: detechedstate и schedpolicy.
#include <рthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr,
int *detachstate);
int pthread_attr_setschedpolicy(pthread_attr_t* attr, int policy);
int pthread_attr_getschedpolicy(const pthread_attr_t *attr, int* policy);
int pthread_attr_setschedparam(pthread_attr_t *attr,
const struct sched_param *param);
int pthread_attr_getschedparam(const pthread_attr_t *attr,
struct sched_param *param);
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inherit);
int pthread_attr_getinheritsched(const pthread_attr_t *attr,
int *inherit);
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
int pthread_attr_getscope(const pthread_attr_t *attr, int *scope);
int pthread_attr_setstacksize(pthread_attr_t *attr, int scope);
int pthread_attr_getstacksize(const pthread_attr_t *attr, int* scope);
Как видите, существует лишь несколько атрибутов, которые вы можете применять, но к счастью у вас, как правило, не возникнет необходимости в использовании большинства из них.
□ detachedstate — этот атрибут позволяет избежать необходимости присоединения потоков (rejoin). Как и большинство этих функций с префиксом _set, эта функция принимает указатель на атрибут и флаг для определения требуемого состояния. Два возможных значения флага для функции attr_setdetachstate — PTHREAD_CREATE_JOINABLE и PTHREAD_CREATE_DETACHED. По умолчанию у атрибута будет значение PTHREAD_CREATE_JOINABLE, поэтому вы сможете разрешить двум потокам объединяться (один ждет завершения другого). Если задать состояние PTHREAD_CREATE_DETACHED, вы не сможете вызвать функцию pthread_join, чтобы выяснить код завершения другого потока.
□ schedpolicy — этот атрибут управляет планированием потоков. Возможные значения — SCHED_OTHER, SCHED_RR и SCHED_FIFO. По умолчанию атрибут равен SCHED_OTHER. Два других типа планирования доступны только для процессов, выполняющихся с правами суперпользователя, поскольку они оба задают планирование в режиме реального времени, но с немного разным поведением. SCHED_RR использует круговую или циклическую схему планирования, a SCHED_FIFO — алгоритм "первым прибыл, первым обслужен". Оба эти алгоритма не обсуждаются в этой книге.
□ schedparam — это напарник атрибута schedpolicy и позволяет управлять планированием потоков, выполняющихся с типом планирования SCHED_OTHER. Мы рассмотрим пример его применения чуть позже в этой главе.
□ inheritsched — этот атрибут принимает одно из двух значений: PTHREAD_EXPLICIT_SCHED и PTHREAD_INHERIT_SCHED. По умолчанию значение атрибута PTHREAD_EXPLICIT_SCHED, что означает планирование, явно заданное атрибутами. Если задать PTHREAD_INHERIT_SCHED, новый поток будет вместо этого применять параметры, используемые потоком, создавшим его.
□ scope — этот атрибут управляет способом вычисления параметров планирования потока. Поскольку ОС Linux в настоящее время поддерживает единственное значение PTHREAD_SCOPE_SYSTEM, мы не будем рассматривать его в дальнейшем.
□ stacksize — этот атрибут управляет размером стека при создании потока, задается в байтах. Это часть необязательного раздела стандарта и поддерживается только в тех реализациях, у которых определено значение _PTHREAD_THREAD_ATTR_STACKSIZE. Linux по умолчанию реализует потоки со стеком большого размера, поэтому этот атрибут в ОС Linux избыточен.
Выполните упражнение 12.5.
Упражнение 12.5. Установка атрибута отсоединенного состоянияВ примере с отсоединенным или обособленным потоком thread5.c вы создаете атрибут потока, задаете состояние потока как отсоединенное и затем создаете с помощью этого атрибута поток. Теперь, когда закончится дочерний поток, он вызовет обычным образом pthread_exit. В это время исходный поток больше не ждет созданный им поток для присоединения. В данном примере используется простой флаг thread_finished, чтобы позволить потоку main определить, закончился ли дочерний поток, и показать, что потоки все еще совместно используют переменные.