pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// восстановить приоритет целевой функции до уровня того,
// кто ее устанавливал, вызывая конструктор
<b> pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);</b>
<b> pthread_attr_setschedparam(&attr, &p->param);</b>
// запуск целевой функции в отдельном "отсоединенном" потоке
pthread_create(NULL, &attr, p->func, NULL);
if (p->statistic()) ++p->sync;
}
}
// 'пустой' обработчик сигнала SIGINT (реакция на ^С)
inline static void empty(int signo) {}
int main(int argc, char **argv) {
// с этой точки стандартная реакция на ^С отменяется...
signal(SIGINT, empty);
// массив целевых функций
void(*funcs[])(void) = { &mon1, &mon2, &mon3 };
// периоды их синхросерий запуска
int period[] = { 317, 171, 77 };
// приоритеты, на которых отрабатывается реакция
// синхросерий на каждый из таймеров синхросерий
int priority[] = { 15, 5, 25 };
int num = sizeof(funcs) / sizeof(*funcs);
// запуск 3-х синхронизированных последовательностей
// выполнения (созданием объектов)
thrblock** tb = new (thrblock*)[num];
for (int i = 0; i < num; i++) {
tb[i] = new thrblock(funcs[i], period[i],
priority[i], true);
if (!tb[i]->OK())
perror("synchro thread create"), exit(EXIT_FAILURE);
}
// ... а теперь ожидаем ^С.
pause();
// подсчет статистики и завершение программы
cout << endl << "Monitoring finalisation!" << endl;
// вывод временных интервалов будем делать в миллисекундах:
const double n2m = 1000000.;
for (int i = 0; i < num, i++) {
timestat *p = &tb[i]->sync;
!(*p); // подсчет статистики по объекту
cout << i << '\t' << p->num << "\t=> " << p->mean / n2m << " [" <<
p->tmin / n2m << "..." << p->tmax / n2m << "]\t~" << p->disp / n2m <<
" (" << p->disp / p->mean * 100 << "%)" << endl;
}
return EXIT_SUCCESS;
}
Вся функциональность программы сосредоточена в одном классе —
thrblock
Ниже представлены отличия нашей реализации от простого цикла с задержкой, обсуждавшейся выше (помимо исправлений очевидных недостатков):
• Для каждого синхронизирующего таймера установлен свой приоритет «пробуждения», и он может быть достаточно высоким, для того чтобы предотвратить вытеснение этого синхронизирующего потока.
• После «пробуждения» по таймеру запускается целевая функция, но выполняется это отдельным потоком, причем потоком «отсоединенным». Другими словами, процесс выполнения целевой функции никак не влияет на общую схему синхронизации.
• Перед запуском целевой функции выполняющему ее потоку восстанавливается приоритет породившего потока (но не потока обслуживания таймера!), ведь нам не нужно, чтобы целевая функция, тем более, возможно и не очень значимая, как в нашем примере, могла влиять вытеснением на процессы синхронизации.
Запустим наше тестовое приложение:
# t3
+10+10*10+10+10.10*10+10+10*10+10+10.10*10+10+10+10*10+10.10+10*10+10+10*10+10.10+10*10+10+10*10+10.10+10+10*10+10+10+10.10+10+10*10+10+10.10*10+10+10+10*10+10.10+10*10+10+10*10+10+10.10*10+10+10*10+10+10.10+10*10+10+10*10+10.10+10*10+10+10*10+10.10+10+10*10+10+10*10^C
Monitoring finalisation!
0 32 => 316.919 [316.867...317.895] ~0.178511 (0.056327%)
1 59 => 170.955 [168.583...173.296] ~0.92472 (0.540914%)
2 132 => 76.9796 [76.942...77.9524] ~0.085977 (0.111688%)
Первое, что мы должны отметить, — это очень приличную точность выдержки периода синхронизации (последняя колонка вывода). Для того чтобы убедиться в том, что целевая функция при этом выполняется под приоритетом породившего ее потока, закомментируем строки, выделенные жирным шрифтом в коде программы:
# t3
+25+25*5+25+25.15*5+25+25*5+25+25.15*5+25+25+25*5+25.15+25*5+25+25*5+25.15+25*5+25+25*5*5+25.15+25+25*5+25+25*5.15+25+25*5+25+25.15*5+25+25+25*5+25.15+25*5+25+25*5+25+25.15*5+25+25*5+25+25^C
Monitoring finalisation!
0 32 => 316.919 [316.797...317.915] ~0.185331 (0.0584792%)
26
Здесь применена только простейшая форма «активного объекта»: гораздо сложнее, к примеру, определить деструктор такого объекта. Но именно в своем простейшем виде это многообещающая техника активных объектов.