IPC через общую память с atomic_t; это хорошо для x86?
У меня есть следующий код для межпроцессного взаимодействия через разделяемую память. Один процесс записывает в журнал, а другой читает его. Один из способов - использовать семафоры, но здесь я использую атомный флаг (log_flag) типа atomic_t, который находится внутри общей памяти. Журнал ( log_datastrong > ) также доступен.
Теперь вопрос в том, будет ли это работать для архитектуры x86 или мне нужны семафоры или мьютексы? Что делать, если я делаю log_flag неатомным? Учитывая, что x86 имеет строгую модель памяти и проактивную когерентность кэша, а оптимизация не применяется к указателям, я думаю, что она все равно будет работать?
EDIT. Обратите внимание: у меня есть многоядерный процессор с 8 ядрами, поэтому у меня нет проблем с занятыми ожиданиями здесь!
// Process 1 calls this function
void write_log( void * data, size_t size )
{
while( *log_flag )
;
memcpy( log_data, data, size );
*log_flag = 1;
}
// Process 2 calls this function
void read_log( void * data, size_t size )
{
while( !( *log_flag ) )
;
memcpy( data, log_data, size );
*log_flag = 0;
}
Ответы
Ответ 1
Вы можете использовать следующий макрос в цикле, чтобы не подчеркивать шину памяти:
#if defined(__x86_64) || defined(__i386)
#define cpu_relax() __asm__("pause":::"memory")
#else
#define cpu_relax() __asm__("":::"memory")
#endif
Кроме того, он действует как барьер памяти ("memory"
param.), поэтому нет необходимости объявлять log_flag
как volatile
.
Но я думаю, что это чересчур, это нужно делать только для жестких вещей в реальном времени. Вы должны быть в порядке, используя futex. И, может быть, вы могли бы просто использовать трубу, достаточно быстро для почти всех целей.
Ответ 2
Я бы не рекомендовал это по двум причинам: во-первых, хотя доступ к указателю не может быть оптимизирован компилятором, это не означает, что указанное значение не будет кэшироваться процессором. Во-вторых, тот факт, что он является атомарным, не будет препятствовать доступу чтения между концом цикла while и строкой, которая делает * log_flag = 0. Мьютекс более безопасен, хотя и намного медленнее.
Если вы используете pthreads, подумайте об использовании мьютекса RW для защиты всего буфера, так что вам не нужен флаг для его контроля, мьютекс сам по себе является флагом, и вы будете иметь лучшую производительность при частом читает.
Я также не рекомендую делать пустые while() петли, вы будете обрабатывать весь процессор таким образом. Поместите прострел (1000) внутри петли, чтобы дать процессору возможность дышать.
Ответ 3
Существует целая куча причин, по которым вам следует использовать семафор и не полагаться на флаг.
- Ваш журнал чтения в то время, когда цикл вращается без необходимости. Это потребляет системные ресурсы, например, излишнюю мощность. Это также означает, что ЦПУ не может использоваться для других задач.
- Я буду удивлен, если x86 полностью гарантирует порядок чтения и записи. входящие данные могут установить флаг журнала в 1 только для того, чтобы исходящие данные установили его в 0. Это может потенциально означать, что вы теряете данные.
- Я не знаю, откуда вы его получили, что оптимизация не применяется к указателям как к общему использованию. Оптимизация может применяться везде, где нет никакой разницы с внешними изменениями. Компилятор, вероятно, не знает, что log_flag можно изменить с помощью параллельного процесса.
Проблема 2 может показаться очень редкой, и отслеживать проблему будет сложно. Так что сделайте себе одолжение и используйте правильные примитивы операционной системы. Они гарантируют, что все будет работать должным образом.
Ответ 4
Пока log_flag
является атомарным, вы будете в порядке.
Если log_flag
был просто обычным bool, у вас нет гарантии, что он будет работать.
Компилятор может изменить порядок инструкций
*log_flag = 1;
memcpy( log_data, data, size );
Это семантически идентично в однопроцессорной системе, пока log_flag
не доступен внутри memcpy
. Ваша единственная экономичная изящество может быть худшим оптимизатором, который не может определить, к каким переменным обращаются в memcpy
.
cpu может изменить порядок ваших инструкций
Он может выбрать загрузку log_flag перед циклом для оптимизации конвейера.
кеш может изменить порядок записи в памяти.
Линия кэша, содержащая log_flag
, может синхронизироваться с другим процессором до строки кэша, содержащей data
.
То, что вам нужно, - это способ сообщить компилятору, процессору и кешу "руки", чтобы они не делали предположений о заказе. Это можно сделать только с заграждением памяти. std::atomic
, std::mutex
, а все семафоры имеют правильные инструкции по сохранению памяти, встроенные в их код.