Чтение онлайн

ЖАНРЫ

Введение в QNX/Neutrino 2. Руководство по программированию приложений реального времени в QNX Realtime Platform

Кёртен Роб

Шрифт:

}

Обратите внимание, что на этот раз мы передали функции pthread_create в качестве первого аргумента указатель на

pthread_t
. Там и будет сохранен идентификатор вновь созданного потока. После того как первый цикл
for
завершится, у нас будет num_cpu работающих потоков, плюс поток, выполняющий main. Потребление ресурсов процессора потоком main нас мало интересует — этот поток потратит все свое время на ожидание.

Ожидание достигается применением функции pthread_join

к каждому из наших потоков. Сначала мы ждем завершения потока
thread_ids[0]
. Когда он завершится, функция pthread_join разблокируется. Следующая итерация цикла
for
заставит нас ждать завершения потока
thread_ids[1]
, и так далее для всех num_cpus потоков.

В этот момент возникает законный вопрос: «А что если потоки завершат работу в обратном порядке?» Другими словами, если имеются 4 процессора, и по какой-либо причине поток, выполняющийся на последнем процессоре (с номером 3), завершит работу первым, затем завершится поток, выполняющийся на процессоре с номером 2, и так далее? Вся прелесть приведенной схемы заключается в том, что ничего плохого не произойдет.

Первое, что произойдет — это то, что pthread_join блокируется на

thread_ids[0]
. Тем временем пусть завершится поток
thread_ids[3]
. Это не окажет абсолютно никакого воздействия на поток main, который будет по-прежнему ждать завершения первого потока. Затем, пусть завершит работу поток
thread_ids[2]
. По-прежнему, никаких последствий. И так далее — пока не завершит работу поток
thread_ids[0]
.

В этот момент pthread_join разблокируется, и мы немедленно переходим к следующей итерации цикла

for
. Вторая итерация цикла for применит pthread_join к потоку
thread_ids[1]
, который не будет блокирован, и итерация завершится немедленно. Почему? Потому что поток, идентифицированный как
thread_ids[1]
, уже завершился. Поэтому наш цикл for просто «проскочит» остальные потоки и завершится. В этот момент мы будем знать, что вычислительные потоки синхронизированы, и теперь мы можем выводить результаты отображение.

Применение барьера

Когда мы говорили о синхронизации функции main по моменту завершения рабочих потоков (в параграфе «Синхронизация по отношению к моменту завершения потока», см. выше), мы упомянули два метода синхронизации: один метод с применением функции pthread_join, который мы только что рассмотрели, и метод с применением барьера.

Возвращаясь к нашей аналогии с процессами в жилом доме, предположим, что семья пожелала где-нибудь отдохнуть на природе. Водитель садится в микроавтобус и запускает двигатель. И ждет. Водитель будет ждать до тех пор, пока все члены семьи не сядут в машину, и только затем можно будет ехать — не можем же мы кого-нибудь оставить!

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

Однако, отметьте для себя одну важную отличительную особенность. С применением функции pthread_join мы ожидаем завершения потоков. Это означает, что на момент ее разблокирования потоков нет больше с нами; они закончили работу и завершились.

В случае с барьером, мы ждем «встречи» определенного

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

Сначала барьер следует создать при помощи функции barrier_init:

#include <sync.h>

int barrier_init(barrier_t *barrier, const barrier_attr_t *attr, int count);

Эта функция создает объект типа «барьер» по переданному ей адресу (указатель на барьер хранится в параметре barrier) и назначает ему атрибуты, которые определены в attr (мы будем использовать NULL, чтобы установить значения по умолчанию). Число потоков, которые должны вызывать функцию barrier_wait, передается в параметре count.

После того как барьер создан, каждый из потоков должен будет вызвать функцию barrier_wait, чтобы сообщить, что он отработал:

#include <sync.h>

int barrier_wait(barrier_t *barrier);

После того как поток вызвал barrier_wait, он будет блокирован до тех пор, пока число потоков, указанное первоначально в параметре count функции barrier_init, не вызовет функцию barrier_wait (они также будут блокированы). После того как нужное число потоков выполнит вызов функции barrier_wait, все эти потоки будут разблокированы «одновременно».

Вот пример:

/*

* barrier1.c

*/

#include <stdio.h>

#include <time.h>

#include <sync.h>

#include <sys/neutrino.h>

barrier_t barrier; // Объект типа «барьер»

void* thread1(void *not_used) {

 time_t now;

 char buf[27];

 time(&now);

 printf("Поток 1, время старта %s", ctime_r(&now, buf));

 // Выполнить вычисления

 // (вместо этого просто сделаем sleep)

 sleep(20);

 barrier_wait(&barrier);

 // После этого момента все потоки уже завершатся

 time(&now);

 printf("Барьер в потоке 1, время срабатывания %s",

ctime_r(&now, buf));

}

void* thread2(void *not_used) {

 time_t now;

 char buf[27];

 time(&now);

 printf("Поток 2, время старта %s", ctime_r(&now, buf));

 // Выполнить вычисления

 // (вместо этого просто сделаем sleep)

 sleep(40);

 barrier_wait(&barrier);

Поделиться с друзьями: