memcpy((void*)buf + atomic_add_value(ind, how), (void*)res, how);
Или даже так:
// глобальные описания, доступные всем потокам
...
//
указательтекущей позиции записи:
volatile unsigned int ind = (unsigned int)buf;
...
// выполняется в каждом из потоков:
memcpy((void*)atomic_add_value(ind, how), (void*)res, how);
В последнем случае это, конечно, трюкачество, построенное на том, что в 32-разрядной архитектуре представления указателя и переменной
unsigned int
совпадают, но это «работающее трюкачество» и работающее иногда весьма эффективно.
Техника применения атомарных операций оказывается крайне удобной, например, при осуществлении вывода, часто диагностического, из различных потоков. Положим, нам нужно в каждом из многих потоков выводить диагностическую строку при достижении ими определенной точки исполнения:
cout << "Это вывод потока " << pthread_self << endl;
Но так делать нельзя: при таком решении у нас появляются 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
#include
#include
#include
#include
#include
#include
#include
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--) {