Рейтинговые книги
Читем онлайн QNX/UNIX: Анатомия параллелизма - Цилюрик Олег Иванович

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 14 15 16 17 18 19 20 21 22 ... 106

Заметим здесь вскользь (в дальнейшем нам представится возможность использовать эти знания), что помимо «продуктивных» потоков (компонент системы и пользовательских приложений) в системе всегда существует один «паразитный» поток, запущенный с приоритетом 0 (idle-поток). Он «выбирает» весь остаток процессорного времени в те периоды, когда все имеющиеся в системе продуктивные потоки перейдут в блокированные состояния (ожидания). Подобная практика хорошо известна и реализуется также в большинстве других операционных систем.

Отличия от POSIX

Если следовать POSIX-стандарту, то некоторые из атрибутов невозможно переопределить до фактического создания этого стандарта (их можно изменить позже в самом коде потока, но иногда это не совсем правильное решение). Все эти возможности относятся к асинхронному завершению потока; детали функционирования этого механизма рассматриваются позже. К подобного рода атрибутам относятся:

• запретить асинхронное завершение (отмену) потока;

• установить тип завершаемости потока;

• определить, что должно происходить при доставке потоку сигналов.

QNX расширяет возможности POSIX, позволяя по условию OR установить соответствующие биты-флаги в поле flagsатрибутной записи, прежде чем будет произведен вызов, создающий поток. Не существует функций вида pthread_attr_set_*(), эквивалентных этим установкам. К этим флагам относятся:

•  PTHREAD_CANCEL_ENABLE— запрос на завершение будет обрабатываться в соответствии с типом завершаемости, установленным для потока (значение по умолчанию);

•  PTHREAD_CANCEL_DISABLE— запросы на завершение будут отложены;

•  PTHREAD_CANCEL_ASYNCHRONOUS— если завершение разрешено, отложенные или текущие запросы будут выполнены немедленно;

•  PTHREAD_CANCEL_DEFERRED— если завершение разрешено, запросы на завершение будут отложены до достижения точки завершаемости (значение по умолчанию);

•  PTHREAD_MULTISIG_ALLOW— завершать по сигналу все потоки в процессе (POSIX-умолчание);

•  PTHREAD_MULTISIG_DISALLOW— завершать по сигналу только тот поток, который принял сигнал.

После запуска потока все атрибуты, связанные с завершаемостью потока, могут быть изменены вызовами pthread_setcancelstate()и pthread_setcanceltype().

Передача параметров потоку

Зачастую каждый поток из группы последовательно создаваемых потоков, выполняющих одну и ту же функцию, нужно запускать со своим индивидуальным блоком данных (параметром потока). Для этого предназначен 4-й параметр вызова pthread_create()— указатель на блок данных типа void*. Характерно, что это может быть произвольная структура данных сколь угодно сложного типа, структуризацию которой вызывающий pthread_create()код и функция потока должны понимать единообразно; никакого контроля соответствия типов на этапе вызова не производится.

Достаточно часто встречающийся на практике образец многопоточного кода — это циклическая процедура ожидания наступления некоторого условия (события), после которого порождается новый экземпляр потока, призванный обслужить наступившее событие (типичная схема всего разнообразия многопоточных сетевых серверов). В таких случаях код, порождающий потоки, выглядит подобно следующему фрагменту:

// функция потока:

void* ThreadProc(void* data) {

 // ... выполняется обработка, используя структуру *(DataParam*)data

 return NULL;

}

// порождающий потоки код:

while (true) {

 // инициализация области параметров

 struct DataParam data(...);

 if ( /* ожидаем нечто */ )

  pthread_create(NULL, &attr, &ThreadProc, &data);

}

Этот простейший код крайне опасен: при быстрых циклах и, что намного важнее, непредсказуемых моментах повторных созданий экземпляров потоков из вызывающего цикла необходимо обеспечить, чтобы используемое в функции потока ThreadProc()значение данных было адекватным. Оно может быть изменено в вызывающем коде или даже, более того, просто разрушено при выходе из локальной области видимости, как в следующем коде:

// порождающий потоки код:

while(true) {

 if ( /* ожидаем нечто */ ) {

  struct DataParam data(...);

  pthread_create(NULL, &attr, &ThreadProc, &data);

 }

 // здесь может идти достаточно длительная обработка

}

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

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

// функция потока:

void* ThreadProc(void* data) {

 DataParam copy = *(DataParam*)data;

 // выполняется обработка, используя структуру copy

 return NULL;

}

или так (если определен инициализирующий конструктор структуры данных):

// функция потока:

void* ThreadProc(void* data) {

 DataParam copy(*(DataParam*)data);

 // ... выполняется обработка, используя структуру copy

 return NULL;

}

Но и этот код оказывается некорректен. При порождении потока нам нужно, чтобы инициализация копии переданных данных в теле функции потока произошла до того, как на очередном цикле оригинал этих данных будет разрушен или изменен. Но дисциплины диспетчеризации равнозначных потоков (в данном случае родительского и порожденного) в операционной системе никак не регламентируют (и не имеют права этого делать!) порядок их выполнения после точки ветвления — pthread_create().

Обеспечить актуальность копии переданных данных можно несколькими искусственными способами:

1. Передачей в качестве аргумента pthread_create()специально сделанной ранее временной копии экземпляра данных, например:

1 ... 14 15 16 17 18 19 20 21 22 ... 106
На этой странице вы можете бесплатно читать книгу QNX/UNIX: Анатомия параллелизма - Цилюрик Олег Иванович бесплатно.
Похожие на QNX/UNIX: Анатомия параллелизма - Цилюрик Олег Иванович книги

Оставить комментарий