`
Читать книги » Книги » Компьютеры и Интернет » Программное обеспечение » Олег Цилюрик - QNX/UNIX: Анатомия параллелизма

Олег Цилюрик - QNX/UNIX: Анатомия параллелизма

1 ... 11 12 13 14 15 ... 67 ВПЕРЕД
Перейти на страницу:

# nice -n-10 time myprog

Значение приоритета создаваемого потока хранится в поле param атрибутной записи (типа sched_param; подробнее эта структура будет рассмотрена при обсуждении диспетчеризации). Для переустановки значений, входящих в структуру sched_param, предоставлена функция:

int pthread_attr_setschedparam(pthread_attr_t* attr,

 const struct sched_param *param);

где attr — как и ранее, атрибутная запись потока;

param — указатель структуры sched_param, из которой параметры будут перенесены в атрибутную запись потока.

Теперь посмотрим, как запустить на выполнение поток с приоритетом на 2 единицы ниже, чем у его родителя:

int policy;

struct sched_param param;

pthread_getschedparam(pthread_self(), &policy, &param);

param sched_priority -= 2;

pthread_attr_t attr;

pthread_attr_init(&attr);

pthread_attr_setschedparam(&attr, &param);

pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);

pthread_create(NULL, &attr, &func, NULL);

Или даже так (хотя это немного грубее):

int policy;

struct sched_param param;

pthread_getschedparam(pthread_self(), &policy, &param);

pthread_attr_t attr;

pthread_attr_init(&attr);

attr.param.sched_priority = param.sched_priority - 2;

pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);

pthread_create(NULL, &attr, &func, NULL);

Примечание

Как и при установке политики диспетчеризации, параметры диспетчеризации потока (и приоритет в их составе) будут установлены, только если параметр типа наследования от родителя установлен в PTHREAD_EXPLICIT_SCHED посредством вызова pthread_attr_setinheritsched().

Заметим здесь вскользь (в дальнейшем нам представится возможность использовать эти знания), что помимо «продуктивных» потоков (компонент системы и пользовательских приложений) в системе всегда существует один «паразитный» поток, запущенный с приоритетом 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() специально сделанной ранее временной копии экземпляра данных, например:

if ( /* нечто */ ) {

 // static обеспечивает неразрушаемость

 static struct DataParam copy;

 copy = data;

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

}

Этот способ иногда хорошо «срабатывает» для данных типа символьных строк, представленных в стандарте языка С (однако используется он не часто):

void* ThreadProc(void *data) {

 ...

 // можно даже не делать копию - это уже копия:

 printf("%s", (char*)data);

}

...

while (true) {

 char *data = ... /* инициализация данных */;

 if ( /* нечто */ )

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

}

2. Для передачи параметра скалярного типа (char, short, int), не превышающего размер указателя, очень часто в самых разнообразных источниках [1, 3] можно увидеть такой трюк, когда указателю присваивается непосредственное значение скалярной величины:

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

void* ThreadProc(void* data) {

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

1 ... 11 12 13 14 15 ... 67 ВПЕРЕД
Перейти на страницу:

Откройте для себя мир чтения на siteknig.com - месте, где каждая книга оживает прямо в браузере. Здесь вас уже ждёт произведение Олег Цилюрик - QNX/UNIX: Анатомия параллелизма, относящееся к жанру Программное обеспечение. Никаких регистраций, никаких преград - только вы и история, доступная в полном формате. Наш литературный портал создан для тех, кто любит комфорт: хотите читать с телефона - пожалуйста; предпочитаете ноутбук - идеально! Все книги открываются моментально и представлены полностью, без сокращений и скрытых страниц. Каталог жанров поможет вам быстро найти что-то по настроению: увлекательный роман, динамичное фэнтези, глубокую классику или лёгкое чтение перед сном. Мы ежедневно расширяем библиотеку, добавляя новые произведения, чтобы вам всегда было что открыть "на потом". Сегодня на siteknig.com доступно более 200000 книг - и каждая готова стать вашей новой любимой. Просто выбирайте, открывайте и наслаждайтесь чтением там, где вам удобно.

Комментарии (0)