Атомарный своп в GNU C ++
-
21-09-2019 - |
Вопрос
Я хочу убедиться, что мое понимание верно.Такого рода вещи очень сложны, поэтому я почти уверен, что мне чего-то не хватает.У меня есть программа, состоящая из потока реального времени и потока, отличного от потока реального времени.Я хочу, чтобы поток, отличный от 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.
Заранее благодарю.
Решение
Обычно не используйте volatile
при написании параллельного кода в C/C++
.Семантика volatile
настолько близки к тому, что вы хотите, что это заманчиво, но, в конце концов, нестабильно недостаточно.К сожалению Java/C# volatile != C/C++ volatile
.У Херба Саттера отличный статья объясняя этот запутанный беспорядок.
Что вам действительно нужно, так это забор памяти. __sync_lock_test_and_set
предоставляет вам ограждение.
Вам также понадобится ограничение памяти, когда вы копируете (загружаете) указатель rt_data в свою локальную копию.
Программирование без блокировки — сложная задача.Если вы хотите использовать расширения C++0x Gcc, это немного проще:
#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 я бы, вероятно, использовал атомарные примитивы, предоставленные Бойкий для этого.Они будут использовать атомарную операцию там, где это возможно, и вернутся к медленной, но правильной реализации на основе мьютекса, если атомарные операции недоступны.Boost может предоставить нечто подобное для C ++.