Атомный обмен в GNU C++
Я хочу убедиться, что мое понимание верно. Подобные вещи хитры, поэтому я почти уверен, что что-то упустил. У меня есть программа, состоящая из потока в реальном времени и потока не в реальном времени. Я хочу, чтобы поток без RT мог поменять местами указатель на память, используемую потоком RT.
Из документов я понимаю, что это может быть достигнуто в g++
с:
// global
Data *rt_data;
Data *swap_data(Data *new_data)
{
#ifdef __GNUC__
// Atomic pointer swap.
Data *old_d = __sync_lock_test_and_set(&rt_data, new_data);
#else
// Non-atomic, cross your fingers.
Data *old_d = rt_data;
rt_data = new_data;
#endif
return old_d;
}
Это единственное место в программе (кроме начальной настройки), где rt_data
модифицируется. когда rt_data
используется в контексте реального времени, оно копируется в локальный указатель. За old_d
позже, когда будет уверено, что старая память не используется, она будет освобождена в потоке без RT. Это правильно? Нужно ли мне volatile
в любом месте? Есть ли другие примитивы синхронизации, которые я должен вызывать?
Кстати, я делаю это в C++, хотя меня интересует, отличается ли ответ для C.
Спасибо заранее
2 ответа
Как правило, не используйте volatile
при написании параллельного кода в C/C++
, Семантика volatile
настолько близки к тому, что вы хотите, что это заманчиво, но в конце концов, изменчивости недостаточно. к несчастью Java/C# volatile != C/C++ volatile
, У Херба Саттера есть отличная статья, объясняющая запутанный беспорядок.
То, что вы действительно хотите, это забор памяти. __sync_lock_test_and_set
обеспечивает ограждение для вас.
Вам также понадобится ограничитель памяти при копировании (загрузке) указателя rt_data в вашу локальную копию.
Программирование без блокировки сложно. Если вы хотите использовать расширения Gcc C++0x, это немного проще:
#include <cstdatomic>
std::atomic<Data*> rt_data;
Data* swap_data( Data* new_data )
{
Data* old_data = rt_data.exchange(new_data);
assert( old_data != new_data );
return old_data;
}
void use_data( )
{
Data* local = rt_data.load();
/* ... */
}
Обновление: этот ответ не правильный, так как мне не хватает того факта, что volatile
гарантирует, что доступ к volatile
переменные не переупорядочены, но не дают таких гарантий в отношении другихvolatile
доступ и манипуляции. Забор памяти дает такие гарантии и необходим для этого применения. Мой оригинальный ответ ниже, но не действуйте на него. См. Этот ответ для хорошего объяснения в дыре в моем понимании, которая привела к следующему неправильному ответу.
Оригинальный ответ:
Да нужно volatile
на ваше rt_data
заявление; всякий раз, когда переменная может быть изменена вне потока управления потока, обращающегося к ней, она должна быть объявленаvolatile
, Хотя вы можете быть в состоянии уйти без volatile
так как вы копируете в локальный указатель, volatile
по крайней мере, помогает с документацией, а также запрещает некоторые оптимизации компилятора, которые могут вызвать проблемы. Рассмотрим следующий пример, принятый из DDJ:
volatile int a;
int b;
a = 1;
b = a;
Если это возможно для a
чтобы его значение изменилось между a=1
а также b=a
, затем a
должен быть объявлен volatile
(если, конечно, не присвоить устаревшее значение b
приемлемо). Многопоточность, особенно с атомарными примитивами, составляет такую ситуацию. Ситуация также вызывается переменными, измененными обработчиками сигналов и переменными, отображаемыми в нечетные ячейки памяти (например, аппаратные регистры ввода / вывода). Смотрите также этот вопрос.
В противном случае, это выглядит хорошо для меня.
В C я бы, вероятно, использовал для этого атомарные примитивы, предоставленные GLib. Они будут использовать атомарную операцию там, где она доступна, и прибегнут к медленной, но правильной реализации на основе мьютекса, если атомарные операции недоступны. Boost может обеспечить нечто подобное для C++.