Атомный обмен в 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++.

Другие вопросы по тегам