Я хочу проверить, что мое понимание корректно. Такого рода вещь хитра, таким образом, я почти уверен, что пропускаю что-то. У меня есть программа, состоящая из потока в реальном времени и потока нев реальном времени. Я хочу, чтобы поток не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
настолько близка к тому, что вы хотите, что это заманчиво, но, в конце концов, 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
, по крайней мере, помогает с документацией, а также препятствует некоторым оптимизациям компилятора, которые могут вызвать проблемы. Рассмотрим следующий пример, взятый из DDJ :
volatile int a;
int b;
a = 1;
b = a;
Если возможно для a
изменить его значение между a = 1
и b = a
, тогда a
следует объявить volatile
(если, конечно, не допустимо присвоение устаревшего значения параметру b
). Многопоточность, особенно с атомарными примитивами, составляет такую ситуацию. Ситуация также запускается переменными, измененными обработчиками сигналов, и переменными, отображаемыми в нечетные области памяти (например, регистры аппаратного ввода-вывода). См. Также этот вопрос .
В остальном, мне кажется, это нормально.
В C я бы, вероятно, использовал для этого атомарные примитивы, предоставленные GLib . Они будут использовать атомарные операции, если они доступны, и вернуться к медленной, но правильной реализации на основе мьютексов, если атомарные операции недоступны. Boost может предоставить нечто подобное для C ++.