Олег Цилюрик - QNX/UNIX: Анатомия параллелизма
- Название:QNX/UNIX: Анатомия параллелизма
- Автор:
- Жанр:
- Издательство:Символ-Плюс
- Год:2006
- Город:Санкт-Петербург
- ISBN:5-93286-088-Х
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Олег Цилюрик - QNX/UNIX: Анатомия параллелизма краткое содержание
Книга адресована программистам, работающим в самых разнообразных ОС UNIX. Авторы предлагают шире взглянуть на возможности параллельной организации вычислительного процесса в традиционном программировании. Особый акцент делается на потоках (threads), а именно на тех возможностях и сложностях, которые были привнесены в технику параллельных вычислений этой относительно новой парадигмой программирования. На примерах реальных кодов показываются приемы и преимущества параллельной организации вычислительного процесса. Некоторые из результатов испытаний тестовых примеров будут большим сюрпризом даже для самых бывалых программистов. Тем не менее излагаемые техники вполне доступны и начинающим программистам: для изучения материала требуется базовое знание языка программирования C/C++ и некоторое понимание «устройства» современных многозадачных ОС UNIX.
В качестве «испытательной площадки» для тестовых фрагментов выбрана ОСРВ QNX, что позволило с единой точки зрения взглянуть как на специфические механизмы микроядерной архитектуры QNX, так и на универсальные механизмы POSIX. В этом качестве книга может быть интересна и тем, кто не использует (и не планирует никогда использовать) ОС QNX: программистам в Linux, FreeBSD, NetBSD, Solaris и других традиционных ОС UNIX.
QNX/UNIX: Анатомия параллелизма - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
static sem_t sem;
...
if (sem_init(&sem, 0, 1) != 0)
perror("semaphore init"), exit(EXIT_FAILURE);
2. Функция потока принимает вид:
void* threadfunc(void* data) {
...
while (i++ != N) {
t1 = ClockCycles();
sem_wait(&sem);
if (debug) str[ind++] = *tid;
sem_post(&sem);
t += ClockCycles() - t1;
sched_yield();
}
...
}
В результате исполнения на этот раз мы получим:
# sy20s -n100000
3 : cycles - 87048886; on semaphore - 870
2 : cycles - 87077787; on semaphore - 870
# sy20s -n1000000
3 : cycles - 869638168; on semaphore — 869
2 : cycles - 868725494, on semaphore - 868
Делаем последнюю модификацию в этой группе тестов, теперь используем специфику именованного семафора ( файл sy20n.cc ):
1. Вместо оператора динамической инициализации неименованного семафора мы теперь должны создать именованный семафор:
static sem_t* sem;
...
const char semname[] = "/duble";
if ((sem = sem_open(semname, O_CREAT, S_IRWX0, 1)) == SEM_FAILED)
perror("semaphore init"), exit(EXIT_FAILURE);
Последний оператор заслуживает отдельного комментария. Техническая документация утверждает, что функция sem_open()
, нормально возвращающая указатель созданного дескриптора семафора типа sem_t
, в случае ошибки возвращает -1 (так было записано и в самых ранних редакциях POSIX). Но использование конструкции вида:
if (sem_open( ... ) == -1)
просто вызовет синтаксическую ошибку (несоответствие типов) и не пройдет компиляцию! Естественнее было бы для такой функции возвращать NULL
в случае ошибки, но... так определено в POSIX. Кроме того, во многих реализациях UNIX определяется константа:
#define SEM_FAILED ((sem_t*)(-1))
В документации QNX она нигде не упоминается, но, как мы видим, она определена, и все прекрасно работает!
2. Функция потока принимает вид (теперь sem
, в отличие от предшествующего случая, ведь теперь это уже указатель на переменную типа sem_t
):
void* threadfunc(void* data) {
...
while (i++ != N) {
t1 = ClockCycles();
sem_wait(sem);
if (debug) str[ind++] = *tid;
sem_post(sem);
t += ClockCycles() - t1;
sched_yield();
}
...
}
3. Теперь особое внимание необходимо уделить не только созданию, но и ликвидации именованного семафора (такой семафор имеет время жизни ядра системы и будет продолжать свое существование и после завершения нашего приложения):
sem_close(sem);
sem_unlink(semname);
Запустим полученное приложение при таком значении -n, которое обеспечит достаточное время его работы. Прежде чем обсуждать полученные результаты, посмотрим отображение семафора на пространство файловых имен системы во время работы приложения:
# ls -l /dev/sem
total 1
n------r-х 1 root root 1 Feb 10 18.56 duble
А теперь и результаты работы программы:
# nice -n-19 sy20n -n100000
3 : cycles - 1453746002, on semaphore - 14537
2 : cycles - 1454203573, on semaphore - 14542
Наконец, мы можем обратиться к количественному анализу полученных цифр:
• Примитивы — мьютекс, неименованный и именованный семафоры, — кажущиеся на первый взгляд сходными, требуют для своего обслуживания в эквивалентных условиях принципиально различных затрат, величины которых радикально отличаются: 140 – 870 – 14500 процессорных циклов соответственно, что соотносится как 1:6,2:104.
• Так же радикально отличаются и их характеристики доступа: изнутри процесса (или даже только из того потока, который уже владеет мьютексом), из внешнего процесса, из процесса, работающего на совершенно другом сетевом узле… То, что мы уже рассматривали как «характеристики времени жизни» объектов, принадлежит различным категориям: процесса (process-persistent), ядра (kernel-persistent) или файловой системы (filesystem-persistent).
• У захваченного мьютекса всегда есть поток-владелец, и только он может освободить его в дальнейшем. Именно поэтому мьютекс может использоваться для синхронизации потоков, но только синхронизации в смысле разграничения временной последовательности доступак фрагменту кода — к тому, что часто называют критической секцией кода. Функциональность семафора значительно выше: при возможности (почти всегда) его применения в том контексте, в котором используется и мьютекс (только нужно ли это делать?), он может применяться и для синхронизации потоков в смысле координации последовательностиих взаимодействия в качестве элемента, управляющего порядком выполнения. Покажем это на примере. Для этого незначительно трансформируем код предыдущего теста для семафора ( файл sy21.cc ):
#include
#include
#include
#include
#include
#include
#include
#include
#include
unsigned long N = 1000;
unsigned int T = 2;
static sem_t* sem;
static bool debug = false;
static char* str; // строка диагностики
static volatile int ind = 0;
uint64_t *t;
void* threadfunc(void* data) {
unsigned long i = 0;
char tid[8];
sprintf(tid, "%X", pthread_self());
// временная метка начала во всех потоках устанавливается
// на время достижения этой точки в последнем (активном) потоке
if ((int)data == T - 1) {
uint64_t с = ClockCycles();
for (int i = 0; i < T; i++ ) t[i] = c;
}
// рабочий цикл переключений за счет синхронизации
while (i++ < N) {
sem_wait(sem + (int)data);
if (debug) str[ind++] = *tid;
sem_post(sem + ((int)data +1) % T);
}
t[(int)data] = ClockCycles() - t[(int)data];
return NULL;
}
int main(int argc, char *argv[]) {
int opt, val;
while ((opt = getopt(argc, argv, "n:t:v")) != -1) {
switch(opt) {
case 'n':
if (sscanf(optarg, "%i", &val) != 1)
cout << "parse command line error" << endl, exit(EXIT_FAILURE);
if (val > 0) N — val;
break;
case 't':
if (sscanf(optarg, "%i", &val) != 1)
cout << "parse command line error" << endl, exit(EXIT_FAILURE);
if (val > 0) T = val;
break;
case 'v':
debug = true;
break;
default:
exit(EXIT_FAILURE);
}
}
if (debug) str = new char[T * N + 1];
pthread_t* tid = new pthread_t[T];
sem = new sem_t[T];
t = new uint64_t[T];
for (int i = 0; i < T; i++) {
// все потоки, кроме последнего, будут заблокированы
// на своих семафорах сразу же после старта
if (sem_init(sem + i, 0, (i == (T - 1)) ? 1 : 0))
perror("semaphore init"), exit(EXIT_FAILURE);
if (pthread_create(tid + i, NULL, threadfunc, (void*)i
! = EOK)
perror( "thread create error"), exit(EXIT_FAILURE);
}
for (int i=0; i < T; i++)
pthread_join(tid[i], NULL);
for (int i = 0; i < T; i++) sem_destroy(sem + i);
delete [] sem;
for (int i = 0; i < T; i++)
cout << tid[i] << "\t: cycles - " << t[i] << ";\ton semaphore - " <<
t[i] / T / N << endl;
delete [] tid;
delete [] t;
if (debug) {
Интервал:
Закладка: