QNX/UNIX: Анатомия параллелизма - Цилюрик Олег Иванович
• При статическомвыполнении (фиксированном количестве параллельных ветвей в приложении) эффективность приложений, построенных на параллельных потоках или параллельных процессах, практически не отличается. Более того, эффективности таких приложений не отличаются и от классической последовательной организации приложения, работающего в одном потоке.
• Существует дополнительный фактор, обеспечивающий «легковесность» потоков в противовес процессам, — это легкость и эффективность их взаимодействия в едином адресном пространстве. В случае процессов для обеспечения таких взаимодействий возникает необходимость привлечения «тяжеловесных» механизмов IPC разнообразной природы (именованные и неименованные каналы, разделяемая память, обмен UNIX-сообщениями и другие). При рассмотрении обмена сообщениями QNX мы еще раз убедимся в том, что обмены и взаимодействия между процессами могут требовать весьма существенных процессорных ресурсов, а при обменах с интенсивным трафиком могут стать доминирующей компонентой, определяющей пределы реальной производительности системы.
Пример: синхронное выполнение кода
Выше приводилось достаточно много подобных примеров, но это были примеры, так сказать, «локальные», фрагментарные, иллюстрирующие использование какой-то одной возможности применительно к потокам. Сейчас мы приведем пример, реализующий часто возникающую на практике возможность. Некоторые программные действия (функции) мы хотели бы запускать периодически с фиксированным временным интервалом T, что весьма напоминает действия и аппаратной реализации, которые должны быть выполнены по каждому импульсу «синхронизирующей последовательности».
Простейшая реализация могла бы выглядеть так:
...
while(true) {
delay(T);
func();
}Но это очень «слабое» решение:
• Задержка, обеспечиваемая функцией пассивной задержки
delay()• Если в системе одновременно с этим приложением работает процесс (поток) более высокого приоритета, то наше приложение может вообще никогда «не проснуться», по крайней мере, пока это не «соизволит» санкционировать параллельное приложение.
• Здесь мы обеспечиваем только одну синхронизированную последовательность вызовов функции
func()• Наконец, время выполнения целевой функции
func()• Более того, если время выполнения функции
func()func()Ниже показано решение, свободное от многих из этих недостатков ( файл t3.cc). Приложение представляет собой тестовую программу, осуществляющую 3 цепочки выполнения различных целевых функций (
mon1mon2mon3period[]
#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) {