IPC через общую память с atomic_t; это хорошо для x86?

У меня есть следующий код для межпроцессного взаимодействия через разделяемую память. Один процесс записывает в журнал, а другой читает его. Один из способов - использовать семафоры, но здесь я использую атомный флаг (log_flag) типа atomic_t, который находится внутри общей памяти. Журнал ( log_data​​strong > ) также доступен.

Теперь вопрос в том, будет ли это работать для архитектуры 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, а все семафоры имеют правильные инструкции по сохранению памяти, встроенные в их код.