KnigaRead.com/

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

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Олег Цилюрик, "QNX/UNIX: Анатомия параллелизма" бесплатно, без регистрации.
Перейти на страницу:

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

• При статическом выполнении (фиксированном количестве параллельных ветвей в приложении) эффективность приложений, построенных на параллельных потоках или параллельных процессах, практически не отличается. Более того, эффективности таких приложений не отличаются и от классической последовательной организации приложения, работающего в одном потоке.

• Существует дополнительный фактор, обеспечивающий «легковесность» потоков в противовес процессам, — это легкость и эффективность их взаимодействия в едином адресном пространстве. В случае процессов для обеспечения таких взаимодействий возникает необходимость привлечения «тяжеловесных» механизмов IPC разнообразной природы (именованные и неименованные каналы, разделяемая память, обмен UNIX-сообщениями и другие). При рассмотрении обмена сообщениями QNX мы еще раз убедимся в том, что обмены и взаимодействия между процессами могут требовать весьма существенных процессорных ресурсов, а при обменах с интенсивным трафиком могут стать доминирующей компонентой, определяющей пределы реальной производительности системы.

Пример: синхронное выполнение кода

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

Простейшая реализация могла бы выглядеть так:

...

while(true) {

 delay(T);

 func();

}

Но это очень «слабое» решение:

• Задержка, обеспечиваемая функцией пассивной задержки delay(), согласно требованиям POSIX не может быть меньше указанного параметра T, но... может быть сколь угодно больше! (В [4] мы писали, что при T = 1 реальная величина задержки будет составлять не 1 мсек., как можно было бы ожидать, а с большой степенью вероятности 3 мсек., и там же мы подробно показывали, как это происходит.)

• Если в системе одновременно с этим приложением работает процесс (поток) более высокого приоритета, то наше приложение может вообще никогда «не проснуться», по крайней мере, пока это не «соизволит» санкционировать параллельное приложение.

• Здесь мы обеспечиваем только одну синхронизированную последовательность вызовов функции func(). А если бы нам потребовалось несколько (много) синхросерий, в каждой из которых выполняется своя функция, а периоды серий не кратны друг другу?

• Наконец, время выполнения целевой функции func() включается в период одного «кругового пробега» цикла, то есть период T отсчитывается от конца предыдущего выполнения функции до начала текущего, а это не совсем то, что мы подразумевали при использовании термина «синхронное».

• Более того, если время выполнения функции func() достаточно флуктуирует от одного вызова до другого (например, из-за изменений данных, с которыми работает функция), то периоды вызовов начинают «гулять», а дисперсия периода результирующей последовательности вызовов func() становится просто непомерно большой.

Ниже показано решение, свободное от многих из этих недостатков (файл t3.cc). Приложение представляет собой тестовую программу, осуществляющую 3 цепочки выполнения различных целевых функций (mon1, mon2, mon3) с разными периодами для каждой цепочки (массив period[]):

Синхронизация выполнения участка кода

#include <stdlib.h>

#include <stdio.h>

#include <unistd.h>

#include <inttypes.h>

#include <errno.h>

#include <iostream.h>

#include <sys/neutrino.h>

#include <sys/syspage.h>

#include <sys/netmgr.h>

#include <pthread.h>

#include <signal.h>

#include <algorithm>


static void out(char s) {

 int policy;

 sched_param param;

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

 cout << s << param.sched_curpriority << flush;

}


// целевые функции каждой из последовательностей только

// выводят свой символ-идентификатор и следующий за ним

// приоритет, на котором выполняется целевая функция

static void mon1(void) { out('.'); }

static void mon2(void) { out('*'); }

static void mon3(void) { out('+'); }


// это всего лишь перерасчет временных интервалов,

// измеренных в тактах процессора (в наносекундах)

inline uint64_t cycles2nsec(uint64_t с) {

 const static uint64_t cps =

  // частота процессора

  SYSPAGE_ENTRY(qtime)->cycles_per_sec;

 return (с * 1000000000) / cps;

}


// структура, необходимая только для накопления статистики параметров

// ряда временных отметок: среднего, среднеквадратичного отклонения,

// минимального и максимального значений

struct timestat {

private:

 uint64_t prev;

public:

 uint64_t num;

 double mean, disp, tmin, tmax;

 timestat(void) {

  mean = disp = tmin = tmax = 0.0;

  num = 0;

 }

 // новая временная отметка в ряду:

 void operator++(void) {

  uint64_t next = ClockCycles(), delta;

  if (num i= 0) {

   double delta = cycles2nsec(next — prev);

   if (num == 1) tmin = tmax = delta;

   else tmin = min(tmin, delta), tmax = max(tmax, delta);

   mean += delta;

   disp += delta * delta;

  }

  prev = next;

  num++;

 }

 // подвести итог ряда;

 void operator !(void) {

  mean /= (num - 1);

  disp = sqrt(disp / (num - 1) - mean * mean);

 }

}


// предварительное описание функции потока объекта

void* syncthread(void*);


class thrblock {

private:

 static int code;

 bool ok, st;

public:

 pthread_t tid;

 struct sigevent event;

 timer_t timer;

 int chid;

 void* (*func)(void*);

 sched_param param;

 // структура только для статистики:

 timestat sync;

 // конструктор класса - он не только инициализирует структуру данных

 // создаваемого объекта, но и запускает отдельный поток для его исполнения

 thrblock(

  // параметры конструктора

  // - целевая функция последовательности

  void (*dofunc)(void);

  // - период ее синхронизации

  unsigned long millisec;

  // - приоритет возбуждения синхросерии

  unsigned short priority;

  // - копить ли статистику временных интервалов?

  bool statist = false

 ) {

  // создание канала для получения уведомлений от таймера

  if (!(ok = ((chid = ChannelCreate(0)) >= 0))) return;

  // создать соединение по каналу, которое будет использовать таймер

  event.sigev_coid =

   ConnectAttach(ND_LOCAL_NODE, 0, chid, NTO_SIDE_CHANNEL, 0);

  if (!(ok = (event.sigev_coid >= 0))) return;

  // занести целевую функцию, заодно выполнив

  // трюк преобразования над ее типом

  func = (void*(*)(void*))dofunc;

  int policy;

  // запомнить приоритет вызывающей программы

  // под этим приоритетом и вызывать целевую функцию

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

  st = statist;

  event.sigev_code = code++;

  event.sigev_notify = SIGEV_PULSE;

  // а вот это приоритет, с которым нужно будет пробуждаться от таймера!

  event.sigev_priority = priority;

  // создание таймера

  if (!(ok = (timer_create(CLOCK_REALTIME, &event, &timer) == 0))) return;

  // запуск отдельного потока, который по сигналу

  // таймера будет выполнять целевую функцию

  if (!(ok = (pthread_create(&tid, NULL, &syncthread, (void*)this) == EOK)))

   return;

  // и только после этого можно установить период срабатывания

  // таймера, после чего он фактически и запускается

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*