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 может предоставить что-то подобное для С++.