Потокобезопасный обмен данными между потоками / разделяемой памятью в C ++ на Linux - PullRequest
2 голосов
/ 19 октября 2011

Я немного запутался: В производстве у нас есть два процесса, взаимодействующих через общую память, часть обмена данными - это long и bool. Доступ к этим данным не синхронизирован. Долгое время все работало нормально и до сих пор работает. Я знаю, что изменение значения не является атомарным, но, учитывая, что эти значения изменяются / к ним обращаются миллионы раз, это должно было произойти?

Вот пример кода, который обменивается числом между двумя потоками:

#include <pthread.h>
#include <xmmintrin.h>

typedef unsigned long long uint64;
const uint64 ITERATIONS = 500LL * 1000LL * 1000LL;

//volatile uint64 s1 = 0;
//volatile uint64 s2 = 0;
uint64 s1 = 0;
uint64 s2 = 0;

void* run(void*)
{
    register uint64 value = s2;
    while (true)
    {
        while (value == s1)
        {
        _mm_pause();// busy spin
        }
        //value = __sync_add_and_fetch(&s2, 1);
        value = ++s2;
    }
 }

 int main (int argc, char *argv[])
 {
     pthread_t threads[1];
     pthread_create(&threads[0], NULL, run, NULL);

     register uint64 value = s1;
     while (s1 < ITERATIONS)
     {
         while (s2 != value)
         {
        _mm_pause();// busy spin
         }
        //value = __sync_add_and_fetch(&s1, 1);
        value = ++s1;
      }
}

Как видите, я прокомментировал несколько вещей:

// volatile uint64 s1 = 0;

и

// value = __sync_add_and_fetch (& s1, 1);

__ sync_add_and_fetch атомарно увеличивает переменную.

Я знаю, что это не очень научно, но несколько раз без синхронизирующих функций работает совершенно нормально. Кроме того, если я измерил синхронизацию обеих версий и без синхронизации они работают с одинаковой скоростью, почему __sync_add_and_fetch не добавляет никаких издержек?

Я предполагаю, что компилятор гарантирует атомарность для этих операций, и поэтому я не вижу проблемы в производстве. Но все еще не могу объяснить, почему __sync_add_and_fetch не добавляет никаких накладных расходов (даже работает в режиме отладки).

Еще несколько подробностей о шахтной среде: Ubuntu 10.04, gcc4.4.3 Intel i5 многоядерный процессор.

Производственная среда похожа, она работает только на более мощных процессорах и на ОС Centos.

спасибо за вашу помощь

Ответы [ 3 ]

9 голосов
/ 19 октября 2011

По сути, вы спрашиваете "почему я не вижу никакой разницы в поведении / производительности между

s2++;

и

__sync_add_and_fetch(&s2, 1);

Хорошо, если вы пойдете и посмотрите на реальный кодсгенерированный компилятором в этих двух случаях, вы увидите, что есть разница - версия s2++ будет иметь простую инструкцию INC (или, возможно, ADD), в то время как версия __sync будет иметь префикс LOCK для этогоинструкция.

Так почему же он работает без префикса LOCK? Ну, хотя в целом префикс LOCK необходим для того, чтобы он работал в любой системе на базе x86, оказывается, что он вам не нужен.Чипы на базе Intel Core, блокировка необходима только для синхронизации между разными процессорами по шине. При работе на одном процессоре (даже с несколькими ядрами) внутренняя синхронизация выполняется без него.

Так зачем вамне видите замедления в случае __sync? Ну, Core i7 является «ограниченным» чипом в том смысле, что он поддерживает только системы с одним сокетом, поэтому вы не можете иметь несколько процессоров.Нет, блокировка никогда не нужна, и фактически процессор просто игнорирует ее полностью.Теперь код на 1 байт больше, что означает, что он может оказать влияние, если вы ограничены в ifetch или декодировании, но это не так, поэтому вы не увидите никакой разницы.

Если бы вы работали на несколькихВ сокетной системе Xeon вы увидите (небольшое) замедление для префикса LOCK, а также можете увидеть (редкие) сбои в не-LOCK версии.

1 голос
/ 19 октября 2011

Я думаю, что компилятор не генерирует атомарность, пока вы не используете некоторые специфичные для компилятора шаблоны, так что это не пойдет.

Если только два процесса используют общую память, обычно проблем не возникает, особенно если кодфрагменты достаточно короткие.Операционная система предпочитает блокировать один процесс и запускать другой, когда это лучше всего (например, ввод / вывод), поэтому она будет запускать один в хорошую точку изоляции, а затем переключаться на следующий.

Попробуйте запустить несколько экземпляровто же самое приложение и посмотрим, что получится.

0 голосов
/ 19 октября 2011

Я вижу, вы используете пример между потоками Martin Martin Thompson.

Я предполагаю, что компилятор гарантирует атомарность для этих операций, и поэтому я не вижу проблем в работе.Но все еще не могу объяснить, почему __sync_add_and_fetch не добавляет никаких издержек (даже работающих в режиме отладки).

Компилятор здесь ничего не гарантирует.Платформа X86, на которой вы работаетеЭтот код, вероятно, потерпит неудачу на интересном оборудовании.

Не уверен, что вы делаете, но C ++ 11 обеспечивает атомарность с помощью std :: atomic.Вы также можете взглянуть на boost :: atomic .Я предполагаю, что вы заинтересованы в паттерне Disruptor, я бесстыдно подключу свой порт к C ++, который называется disruptor - .

...