355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Нейл Мэтью » Основы программирования в Linux » Текст книги (страница 47)
Основы программирования в Linux
  • Текст добавлен: 21 сентября 2016, 17:59

Текст книги "Основы программирования в Linux"


Автор книги: Нейл Мэтью


Соавторы: Ричард Стоунс
сообщить о нарушении

Текущая страница: 47 (всего у книги 67 страниц)

Атрибуты потока

Когда мы начали рассматривать потоки, то не обсуждали более сложную тему – атрибуты потока. Теперь, рассказав о синхронизации потоков – ключевой теме главы, мы можем вернуться назад и остановиться на этих характеристиках потока. Существует лишь несколько атрибутов потока, которыми вы можете управлять; здесь мы собираемся обсудить только те, которые вам понадобятся, скорее всего. Подробную информацию о других атрибутах вы можете найти в интерактивном справочном руководстве.

Во всех предыдущих примерах вы должны были повторно синхронизовать потоки с помощью функции pthread_join, прежде чем разрешить программе завершить выполнение. Это необходимо сделать, если вы хотите, чтобы один поток вернул данные другому потоку, создавшему данный. Иногда вам не нужно ни возвращать информацию из второго потока в поток main, ни заставлять поток main ждать этого.

Предположим, что вы создаете второй поток для записи в буфер резервной копии файла данных, который редактируется, пока поток main продолжает обслуживать пользователя. Когда создание копии закончено, второй поток может тут же завершиться. Ему не нужно присоединяться к потоку main.

Вы можете создать потоки, ведущие себя подобным образом. Они называются отсоединенными или обособленными потоками, и вы создаете их, изменяя атрибуты потока или вызывая функцию pthread_detach. Поскольку мы хотим продемонстрировать атрибуты, то применим здесь первый метод.

Самая важная функция, которая вам понадобится, – pthread_attr_init, инициализирующая объект атрибутов потока:

#include

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_setdetachstatePTHREAD_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 определить, закончился ли дочерний поток, и показать, что потоки все еще совместно используют переменные.

#include

#include

#include

#include

void *thread_function(void *arg);

char message[] = «Hello World»;

int thread_finished = 0;

int main() {

 int res;

 pthread_t a_thread;

 pthread_attr_t thread_attr;

 res = pthread_attr_init(&thread_attr);

 if (res != 0) {

  perror(«Attribute creation failed»);

  exit(EXIT_FAILURE);

 }

 res = pthread_attr_setdetachstate(&thread_attr,

  PTHREAD_CREATE_DETACHED);

 if (res != 0) {

  perror(«Setting detached attribute failed»);

  exit(EXIT_FAILURE);

 }

 res = pthread_create(&a_thread, &thread_attr,

  thread_function, (void *)message);

 if (res != 0) {

  perror(«Thread creation failed»);

  exit(EXIT_FAILURE);

 }

 (void)pthread_attr_destroy(&thread_attr);

 while (!thread_finished) {

  printf(«Waiting for thread to say it's finished...n»);

  sleep(1);

 }

 printf(«Other thread finished, bye!n»);

 exit(EXIT_SUCCESS);

}

void *thread_function(void *arg) {

 printf(«thread_function is running. Argument was %sn», (char *)arg);

 sleep(4);

 printf(«Second thread setting finished flag, and exiting nown»);

 thread_finished = 1;

 pthread_exit(NULL);

}

Вывод не принесет сюрпризов:

$ ./threads

Waiting for thread to say it's finished...

thread_function is running. Argument was Hello World

Waiting for thread to say it's finished...

Waiting for thread to say it's finished...

Waiting for thread to say it's finished...

Second thread setting finished flag, and exiting now

Other thread finished, bye!

Как видите, установка отсоединенного состояния позволяет второму потоку завершиться независимо, без необходимости исходному потоку ждать этого события.

Как это работает

В исходном тексте программы два важных фрагмента:

pthread_attr_t thread_attr;

res = pthread_attr_init(&thread_attr);

if (res != 0) {

 perror(«Attribute creation failed»);

 exit(EXIT_FAILURE);

}

который объявляет атрибут потока и инициализирует его, и

res = pthread_attr_setdetachstatе(&thread_attr, PTHREAD_CREATE_DETACHED);

if (res != 0) {

 perror(«Setting detached attribute failed»);

 exit(EXIT_FAILURE);

}

который устанавливает значения атрибутов для задания отсоединенного состояния потока.

К другим незначительным отличиям относится создание потока с передачей адреса атрибутов:

res = pthread_create(&a_thread, &thread_attr, thread_function, (void*)message);

и для завершенности уничтожение атрибутов после их использования:

pthread_attr_destroy(&thread_attr);

Атрибуты планирования потока

Давайте рассмотрим второй атрибут потока, который вам, возможно, захочется изменить, – атрибут планирования. Изменение этого атрибута очень похоже на установку отсоединенного состояния потока, но есть дополнительные функции, которые можно применять для подбора допустимых уровней приоритета, sched_get_priority_max и sched_get_priority_min.

Выполните упражнение 12.6.

Упражнение 12.6. Планирование

Поскольку данная программа thread6.c очень похожа на программу предыдущего упражнения, мы рассмотрим только отличия.

1. Прежде всего, вам понадобится несколько дополнительных переменных:

int max_priority;

int min_priority;

struct sched_param scheduling_value;

2. После того как установлен атрибут отсоединения, вы задаете политику планирования:

res = pthread_attr_setschedpolicy(&thread_attr, SCHED_OTHER);

if (res != 0) {

 perror(«Setting scheduling policy failed»);

 exit(EXIT_FAILURE);

}

3. Далее находите диапазон допустимых приоритетов

max_priority = sched_get_priority_max(SCHED_OTHER);

min_priority = sched_get_priority_min(SCHED_OTHER);

и задаете один из них:

scheduling_value.sched_priority = min_priority;

res = pthread_attr_setschedparam(&thread_attr, &scheduling_value);

if (res != 0) {

 perror(«Setting scheduling priority failed»);

 exit(EXIT_FAILURE);

}

Когда вы запустите программу, то получите следующий вывод:

$ ./thread6

Waiting for thread to say it's finished...

thread_function is running. Argument was Hello World

Waiting for thread to say it's finished...

Waiting for thread to say it's finished...

Waiting for thread to say it's finished...

Second thread setting finished flag, and exiting now

Other thread finished, bye!

Как это работает

Этот пример очень похож на установку атрибута отсоединенного состояния за исключением того, что вы задаете вместо него способ планирования.

Отмена потока

Иногда требуется, чтобы один поток попросил другой завершиться досрочно способом, очень похожим на отправку ему сигнала. Сделать это можно с помощью потоков и параллельно с помощью обработки сигнала; у потоков появляется возможность изменить свое поведение, когда их просят завершиться.

Давайте сначала рассмотрим функцию для создания запроса на завершение потока.

#include

int pthread_cancel(pthread_t thread);

Она достаточно проста: имея идентификатор потока, вы можете запросить его аннулирование. На приемном конце запроса на отмену все немного сложнее, но не слишком. Поток может установить состояние отмены с помощью функции pthread_setcancelstate.

#include

int pthread_setcancelstate(int state, int *oldstate);

Первый параметр равен либо значению PHTREAD_CANCEL_ENABLE, позволяющему получать запросы на отмену, либо PTHREAD_CANCEL_DISABLE, заставляющему игнорировать подобные запросы. Указатель oldstate дает возможность получить предыдущее состояние. Если оно вас не интересует, можно просто передать в этом параметре NULL. Если запросы на отмену принимаются, есть второй уровень управления, принимаемый потоком, – тип отмены, который задается функцией pthread_setcanceltype.

#include

int pthread_setcanceltype(int type, int *oldtype);

Тип отмены может принимать одно из следующих значений: PTHREAD_CANCEL_ASYNCHRONOUS, заставляющее обрабатывать запросы на отмену немедленно, и PTHREAD_CANCEL_DEFERRED, заставляющее запросы на отмену ждать, пока поток не выполнит одну из следующих функций: pthread_join, pthread_cond_wait, pthread_cond_timedwait, pthread_testcancel, sem_wait или sigwait.

Мы не описываем все эти функции в данной главе, поскольку, как правило, не все они нужны. Когда они понадобятся, вы сможете найти дополнительную информацию на страницах интерактивного справочного руководства.

Примечание

В соответствии со стандартом POSIX системные вызовы, способные задерживать выполнение, такие как read, wait и т.д., должны также быть точками отмены потока. Во время написания книги поддержка этого стандарта в ОС Linux представлялась незавершенной. Но кое-какая работа была проделана, скажем, некоторые задерживающие вызовы, такие как sleep, на самом деле допускают отмену. Для того чтобы обезопасить себя, добавляйте вызовы pthread_testcancel в программный код, который по вашим расчетам может быть отменен.

Параметр oldtype позволяет получить предыдущее состояние, если оно вас не интересует, можно передать NULL. По умолчанию потоки запускаются с состоянием отмены, равным PTHREAD_CANCEL_ENABLE, и типом отмены – PTHREAD_CANCEL_DEFERRED.

Выполните упражнение 12.7.

Упражнение 12.7. Отмена потока

Программа thread7.c – ещё один потомок программы thread1.с. На этот раз основной поток отправляет запрос на отмену потока, который он создал.

#include 

#include 

#include 

#include 

void *thread_function(void *arg);

int main() {

 int res;

 pthread_t a_thread;

 void *thread_result;

 res = pthread_create(&a_thread, NULL, thread_function, NULL);

 if (res != 0) {

  perror(«Thread creation failed»);

  exit(EXIT_FAILURE);

 }

 sleep(3);

 printf(«Canceling thread...n»);

 res = pthread_cancel(a_thread);

 if (res != 0) {

  perror(«Thread cancelation failed»);

  exit(EXIT_FAILURE);

 }

 printf(«Waiting for thread to finish...n»);

 res = pthread_join(a_thread, &thread_result);

 if (res != 0) {

  perror(«Thread join failed»);

  exit(EXIT_FAILURE);

 }

 exit(EXIT_SUCCESS);

}

void *thread_function(void *arg) {

 int i, res;

 res = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

 if (res != 0) {

  perror(«Thread pthread_setcancelstate failed»);

  exit(EXIT_FAILURE);

 }

 res = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);

 if (res != 0) {

  perror{"Thread pthread_setcanceltype failed");

  exit(EXIT_FAILURE);

 }

 printf(«thread_function is runningn»);

 for(i = 0; i < 10; i++) {

  printf(«Thread is still running (%d)...n», i);

  sleep(1);

 }

 pthread_exit(0);

}

Когда вы выполните эту программу, то увидите следующий вывод, демонстрирующий отмену потока:

$ ./thread7

thread_function is running

Thread is still running (0)...

Thread is still running (1)...

Thread is still running (2)...

Canceling thread...

Waiting for thread to finish...

$

Как это работает

После того как новый поток был создан обычным способом, основной поток засыпает (чтобы дать новому потоку время для запуска) и затем отправляет запрос на отмену потока.

sleep(3);

printf(«Cancelling thread...n»);

res = pthread_cancel(a_thread);

if (res != 0) {

 perror(«Thread cancelation failed»);

 exit(EXIT_FAILURE);

}

В созданном потоке вы сначала задаете состояние отмены, чтобы разрешить отмену потока:

res = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

if (res != 0) {

 perror(«Thread pthread_setcancelstate failed»);

 exit(EXIT_FAILURE);

}

Далее вы задаете тип отмены PTHREAD_CANCEL_DEFERRED:

res = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);

if (res != 0) {

 perror(«Thread pthread_setcanceltype failed»);

 exit(EXIT_FAILURE);

}

И в конце поток ждет отмену:

for (i = 0; i < 10; i++) {

 printf(«Thread is still running (%d)...n», i);

 sleep(1);

}

Потоки в изобилии

До настоящего момента у нас всегда был обычный поток исполнения программы, создающий еще только один поток. Тем не менее мы не хотим, чтобы вы думали, что можно создать только один дополнительный поток (упражнение 12.8).

Упражнение 12.8. Много потоков

В заключительном примере этой главы thread8.c мы покажем, как создать несколько потоков в одной и той же программе и затем собрать их снова в последовательности, отличающейся от порядка их создания.

#include

#include

#include

#include

#define NUM_THREADS 6

void *thread_function(void *arg);

int main() {

 int res;

 pthread_t a_thread[NUM_THREADS];

 void *thread_result;

 int lots_of_threads;

 for (lots_of_threads = 0; lots_of_threads < NUM_THREADS; lots_of_threads++) {

  res = pthread_create(&(a_thread[lots_of_threads]), NULL, thread_function, (void*)&lots_of_threads);

  if (res != 0) {

   perror(«Thread creation failed»);

   exit(EXIT_FAILURE);

  }

  sleep(1);

 }

 printf(«Waiting for threads' to finish...n»);

 for(lots of_threads = NUM_THREADS – 1; lots_of_threads >= 0; lots_of_threads–) {

  res = pthread_join(a_thread[lots_of_threads], &thread_result);

  if (res == 0) {

   printf(«Picked up a threadn»);

  } else {

   perror(«pthread_join failed»);

  }

 }

 printf(«All donen»);

 exit(EXIT_SUCCESS);

}

void *thread_function(void *arg) {

 int my_number = *(int*)arg;

 int rand_num;

 printf(«thread_function is running. Argument was %dn», my_number);

 rand_num = 1 + (int)(9.0*rand() / (RAND_MAX+1.0));

 sleep(rand_num);

 printf(«Bye from %dn», my_number);

 pthread_exit(NULL);

}

Выполнив эту программу, вы получите следующий вывод:

$ ./thread8

thread_function is running. Argument was 0

thread_function is running. Argument was 1

thread_function is running. Argument was 2

thread_function is running. Argument was 3

thread_function is running. Argument was 4

Bye from 1

thread_function is running. Argument was 5

Waiting for threads to finish...

Bye from 5

Picked up a thread

Bye from 0

Bye from 2

Bye from 3

Bye from 4

Picked up a thread

Picked up a thread

Picked up a thread

Picked up a thread

Picked up a thread

All done

Как видите, вы создали много потоков и разрешили им завершаться в произвольной последовательности. В этой программе есть маленькая ошибка, которая проявит себя, если вы удалите вызов sleep из цикла, запускающего потоки. Мы включили ее, чтобы показать, как вы должны быть внимательны при написании программ, применяющих потоки. Вы нашли ее? В следующем разд. «Как это работает» будет дано объяснение.

Как это работает

На сей раз вы создаете массив идентификаторов потоков:

pthread_t a_thread[NUM_THREADS];

и заключаете в цикл создание нескольких потоков:

for (lots_of_threads = 0; lots_of_threads < NUM_THREADS; lots_of_threads++) {

 res = pthread_create(&(a_thread[lots_of_threads]), NULL,

  thread_function, (void *)&lots_of_threads);

 if (res != 0) {

  perror(«Thread creation failed»);

  exit(EXIT_FAILURE);

 }

 sleep(1);

}

Затем потоки сами по себе ждут в течение случайного промежутка времени, прежде чем начать выполнение:

void *thread_function(void *arg) {

 int my_number = *(int *)arg;

 int rand_num;

 printf(«thread_function is running. Argument was %dn», my_number);

 rand_num = 1+(int)(9.0* rand()/(RAND_MAX+1.0));

 sleep(randnum);

 printf(«Bye from %dn», my_number);

 pthread_exit(NULL);

}

В это время в основном (исходном) потоке вы ждете, чтобы собрать потоки, но не в том порядке, в каком вы их создали:

for (lots_of_threads = NUM_THREADS – 1; lots_of_threads >= 0; lots_of_threads–) {

 res = pthread_join(a_thread[lots_of__threads], &thread_result);

 if (res == 0) {

  printf(«Picked up a threadn»);

 } else {

  perror(«pthread_join failed»);

 }

}

Если вы попробуете выполнить программу без вызова sleep, то увидите странный эффект: некоторые потоки запускаются с одним и тем же номером, например, вы можете получить вывод, похожий на следующий:

thread_function is running. Argument was 0

thread_function is running. Argument was 2

thread_function is running. Argument was 2

thread_function is running. Argument was 4

thread_function is running. Argument was 4

thread_function is running. Argument was 5

Waiting for threads to finish...

Bye from 5

Picked up a thread

Bye from 2

Bye from 0

Bye from 2

Bye from 4

Bye from 4

Picked up a thread

Picked up a thread

Picked up a thread

Picked up a thread

Picked up a thread

All done

Вы догадались, что произошло? Потоки запускаются, используя локальную переменную как аргумент функции потока. Эта переменная обновляется в цикле. Далее приведены ошибочные строки:

for (lots_of_threads = 0; lots_of_threads < NUM_THREADS; lots_of_threads++) {

 res = pthread_create(&(a_thread[lots_of_threads]), NULL,

  thread_function, (void *)&lots_of_threads);

Если поток main выполняется достаточно быстро, он может искажать аргумент (lots_of_threads) для некоторых потоков. Поведение, подобное этому, наблюдается, когда недостаточно внимания уделяется совместно используемым переменным и множественным путям исполнения (multiple execution paths). Мы предупреждали вас о том, что программирование потоков требует повышенного внимания при разработке! Для исправления ошибки вам следует передавать непосредственно значение следующим образом:

res = pthread_create(&(a_thread[lots_of_threads]), NULL,

 thread_function, (void *)lots_of_threads);

и конечно изменить thread_function:

void *thread_function(void *arg) {

 int my_number = (int)arg;

Все исправления, выделенные цветом, показаны в программе thread8a.c.

#include

#include

#include

#include

#include

#define NUM_THREADS 6

void *thread_function(void *arg);

int main() {

 int res;

 pthread_t a_thread[NUM_THREADS];

 void *thread_result;

 int lots_of_threads;

 for (lots_of_threads = 0; lots_of_threads < NUM_THREADS; lots_of_threads++) {

  res = pthread_create(&(a_thread[lots_of_thread]), NULL,

   thread_function, (void*)lots_оf_threads);

  if (res != 0) {

   perror(«Thread creation failed»);

   exit(EXIT_FAILURE);

  }

 }

 printf(«Waiting for threads to finish...n»);

 for (lots_of_threads = NUM_THREADS – 1; lots_of_threads >= 0;

  lots of threads–) {

  res = pthread_join(a_thread[lots_of_threads], &thread_result);

  if (res == 0) {

   printf(«Picked up a threadn»);

  } else {

   perror(«pthread_join failed»);

  }

 }

 printf(«All donen»);

 exit(EXIT_SUCCESS);

}

void* thread_function(void* arg) {

 int my_number = (int)arg;

 int rand_num;

 printf(«thread_function is running. Argument was %dn», my_number);

 rand_num = 1+(int)(9.0*rand()/(RAND_MAX+1.0));

 sleep(rand_num);

 printf(«Bye from %dn», my_number);

 pthread_exit(NULL);

}


    Ваша оценка произведения:

Популярные книги за неделю