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) {
str[ind] = "\0"; cout << str << endl;
delete [] str;
}
exit(EXIT_SUCCESS);
}
Логически приложение изменилось следующим образом:
• Теперь у нас может быть не 2 идентичных (симметричных) потока, а произвольное их количество (ключ
-t
при запуске приложения).
• Потоки синхронизируются не на одном семафоре — введен массив семафоров по числу потоков: каждый поток блокируется на «своем» семафоре, но разблокирует его (после очередного выполнения своего фрагмента) семафор заблокированного «соседа».
• Теперь нам нет нужды использовать барьер для одновременного старта всех созданных потоков: семафоры всех создаваемых потоков инициализируются нулевым значением; стартующий поток тут же блокируется на своем семафоре, и только последний из запущенных выполняется, не блокируясь на семафоре.
• Из кода исключены какие бы то ни было средства принудительной передачи управления (
sched_yield
) — все управление логикой ветвления осуществляется только состояниями семафоров.
Посмотрим, что у нас получилось. Запускаем приложение с диагностическим выводом идентификаторов потоков (ключ
-v
; он у нас был в тестах и ранее, только мы о нем не упоминали):
# nice -n-19 sy21 -n20 -t12 -v
2 : cycles - 664874; on semaphore - 2770
3 : cycles - 649150; on semaphore - 2704
4 : cycles - 638906, on semaphore - 2662
5 : cycles - 622987; on semaphore - 2595
6 : cycles - 611781; on semaphore - 2549
7 : cycles - 594515; on semaphore - 2477
8 : cycles - 571003; on semaphore - 2379
9 : cycles - 552834; on semaphore - 2303
10 : cycles - 536817; on semaphore - 2236
11 : cycles - 519357; on semaphore - 2163
12 : cycles - 500388; on semaphore - 2084
13 : cycles - 296633; on semaphore - 1235
D23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABCD23456789ABC
В строке диагностики (внизу) хорошо видно регулярное чередование выполняющихся потоков, причем оно начинается с индекса последнего созданного потока (13 в показанном примере). Теперь проделаем то же самое, но на большой выборке и с отключенной диагностикой, чтобы получить «чистое» время контекстных переключений потоков, не искаженное затратами на операции формирования диагностики (результаты для нескольких различных размерностей задачи при разном количестве потоков):
# nice -n-19 sy21 -n100000 -t12
2 : cycles - 1509597589; on semaphore - 1257
3 : cycles - 1509581545; on semaphore - 1257
4 : cycles - 1509570283; on semaphore - 1257
5 : cycles - 1509552472; on semaphore - 1257
6 : cycles - 1509537934; on semaphore - 1257
7 : cycles - 1509519299; on semaphore - 1257
8 : cycles - 1509502312; on semaphore - 1257
9 : cycles - 1509482667; on semaphore - 1257