Atomic swap в GNU С++
Я хочу проверить правильность моего понимания. Подобная вещь сложна, поэтому я почти уверен, что чего-то не хватает. У меня есть программа, состоящая из потока в реальном времени и потока в реальном времени. Я хочу, чтобы поток non-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.
Спасибо заранее.
Ответы
Ответ 1
Как правило, не используйте volatile
при написании параллельного кода в C/C++
. Семантика volatile
настолько близка к тому, что вы хотите, чтобы заманчиво, но в конечном итоге volatile недостаточно. К сожалению Java/C# volatile != C/C++ volatile
. Herb Sutter имеет отличную статью , объясняющую путаницу.
То, что вы действительно хотите, - это забор памяти. __sync_lock_test_and_set
обеспечивает фехтование для вас.
Вам также понадобится забор памяти, когда вы скопируете (загрузите) указатель rt_data на свою локальную копию.
Блокировка свободного программирования сложна. Если вы хотите использовать расширения Gcc С++ 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();
/* ... */
}
Ответ 2
Обновить. Этот ответ неверен, так как мне не хватает того факта, что 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 может предоставить что-то подобное для С++.