Но так делать нельзя: при таком решении у нас появляются 2 проблемы, одна из которых очевидна, а другая — не очень:
1. Выводы разных потоков могут «смешиваться»; более того, за счет буферизации вывода некоторые «рваные» фрагменты мы будем наблюдать дважды. Одним словом, наш вывод окажется полностью «нечитабельным».
2. При осуществлении вывода в контексте потока почти наверняка в процессе вывода будут выполняться системные вызовы, которые потребуют новой диспетчеризации и приведут к вытеснению исходного потока. При этом порядок передачи управления от потока к потоку при наличии отладочного вывода будет отличаться от порядка при его отсутствии. А это, наверное, не совсем то, что мы ожидали. В результате при наличии отладочного вывода мы можем наблюдать совсем не ту картину, для изучения которой этот вывод, собственно, и предназначен.
Эти эффекты не связаны с какой-то конкретной формой вывода, такой как вывод в поток, показанный выше; точно так же будет вести себя и традиционный вызов printf(...)
.
Проблема очень просто решается, если вместо непосредственного вывода из потоков последовательно сбрасывать все сообщения в промежуточный буфер, который будет выводиться в те периоды времени программы, когда запись в него не производится:
const int N = ... , M = ...;
char buf[N];
volatile unsigned ind = 0;
...
// а вот это производится из каждого потока
char tbuf[M];
sprintf(tbuf, 'Это вывод потока %X', pthread_self());
strcpy(buf + atomic_add_value(ind, strlen(tbuf)), tbuf);
И наконец, последнее: есть ли смысл во введении этого дополнительного механизма, если всегда существует альтернативная форма организации такой же защиты доступа посредством критической секции (например, при использовании мьютекса)? Сравним (
#include <stdlib.h>
#include <stdio.h>
#include <iostream.h>
#include <unistd.h>
#include <inttypes.h>
#include <pthread.h>
#include <sys/neutrino.h>
#include <atomic.h>
int main(int argc, char *argv[]) {
uint64_t N = 100000;
bool atom = false, value = false;
int opt, val;
while ((opt = getopt(argc, argv, 'n:av')) != -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 'a':
atom = true;
break;
// использовать форму, возвращающую значение
case 'v':
value = true;
break;
default:
exit(EXIT_FAILURE);
}
}
// замеряется количество процессорных циклов для каждого случая
uint64_t i = N, t = ClockCycles();
volatile unsigned ind = 0;
if (!atom) {
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
while (i--) {
pthread_mutex_lock(&mutex);
ind++;
pthread_mutex_unlock(&mutex);
}
} else if (value)
while (i--) atomic_add_value(&ind, 1);
else while (i--) atomic_add(&ind, 1);
t = ClockCycles() - t;
cout << 'all cycles - ' << t << '; on operation - '
<< t / N << endl;
exit(EXIT_SUCCESS);
}
Вот результат при использовании критической секции:
# nice -n-19 a1 -n10000000
all cycles - 1120872156; on operation - 112
Результат с применением атомарной операции, не возвращающей значения:
# nice -n-19 a1 -n10000000 -a
all cycles — 391018203; on operation - 39
Результат с применением атомарной операции, возвращающей значение (обещанная разница составляет порядка 10%):
# nice -n-19 a1 -n10000000 -a -v
all cycles - 441158981; on operation - 44
Условная переменная
Одним из важнейших принципов использования мьютексов является максимальное сокращение размеров критической секции, то есть участка, который потоки должны проходить последовательно. Однако