LockFree Queue с встроенными Gcc - PullRequest
0 голосов
/ 23 ноября 2018

Я работаю над созданием единственной очереди записи / чтения lock_free, которая помещается в общую память, которая будет открыта двумя разными процессами Linux.Оба процесса открывают shm с помощью MAP_SHARED.

Очередь выглядит следующим образом:

typedef stuct {
    unsigned char Data[ 256 ];
} Element_Type;
typedef struct {
    unsigned int Snd_Cnt;
    unsigned int Rcv_Cnt;
    Element_Type Elems[ 100 ];
} Queue_Type;

OBS: Оба процесса Linux открывают одну и ту же разделяемую память и рассматривают ее как указатель на тип Queue_Type.Допустим, указатель Shm_p.

Процесс Linux, который является автором, делает так:

Tmp_Snd_Cnt = (Shm_p->Snd_Cnt + 1u) % 100;
__atomic_load(&Shm_p>Rcv_Cnt, &Tmp_Rcv_Cnt, __ATOMIC_ACQUIRE);
if (Tmp_Snd_Cnt != Tmp_Rcv_Cnt) {
    memcpy(&Shm_p->Elems[ Tmp_Snd_Cnt ].Data[ 0 ], pointer_to_real_data, size_of_real_data <= 256);
    __atomic_store(&Shm_p>Snd_Cnt, &Tmp_Snd_Cnt, __ATOMIC_RELEASE);
}

Процесс Linux, который является читателем, делает так:

Tmp_Rcv_Cnt = Shm_p->Rcv_Cnt;
__atomic_load(&Shm_p>Snd_Cnt, &Tmp_Snd_Cnt, __ATOMIC_ACQUIRE);
if (Tmp_Snd_Cnt != Tmp_Rcv_Cnt) {
    pointer_to_real_data = &Shm_p->Elems[ Tmp_Rcv_Cnt ].Data[ 0 ];
    Tmp_Rcv_Cnt = (Tmp_Rcv_Cnt + 1u) % 100;
    __atomic_store(&Shm_p>Rcv_Cnt, &Tmp_Rcv_Cnt, __ATOMIC_RELEASE);
}

У меня вопрос: видите ли вы какие-либо проблемы с таким подходом, как гонки и прочее?

То, что я видел, заключается в следующем: иногда читатель получает pointer_to_real_data, чтобы указывать на необновленные данные.Это говорит о том, что GCC поместил memcpy после atomic_store (т.е. переупорядочение команд во время компиляции).Чтобы смягчить это, я поместил asm volatile("" : : : "memory") прямо перед атомарным хранилищем в коде писателя.Читатель получил правильные данные.

Чтобы сделать вещи еще более странными, я забрал переменную и снова скомпилировал.Код начал работать.(Я очистил shm перед повторным тестированием).

Таким образом, очевидно, что компилятор сделал что-то странное в первом случае, без asm volatile, когда читатель получил необновленные данные.Что могло бы быть?

Теперь, чтобы быть в безопасности, у меня есть asm volatile, чтобы дать указание компилятору не менять порядок кода.

Заранее спасибо.

ПОСЛЕДНЕЕ РЕДАКТИРОВАНИЕ (чтобы уменьшить проблему с перезаписью pointer_to_real_data в случае, когда считыватель замедляется после сохранения Rcv_Cnt):

typedef stuct {
    unsigned char Data[ 256u ];
} Base_Element_Type;
typedef stuct {
    unsigned int      Idx;
    Base_Element_Type Base_Elems[ 2u ];
} Element_Type;
typedef struct {
    unsigned int Snd_Cnt;
    unsigned int Rcv_Cnt;
    Element_Type Elems[ 100u ];
} Queue_Type;

Код записывающего устройства:

Tmp_Snd_Cnt = (Shm_p->Snd_Cnt + 1u) % 100u;
__atomic_load(&Shm_p>Rcv_Cnt, &Tmp_Rcv_Cnt, __ATOMIC_ACQUIRE);
if (Tmp_Snd_Cnt != Tmp_Rcv_Cnt) {
    Elem_p = &Shm_p->Elems[ Tmp_Snd_Cnt ];
    memcpy(&Elem_p->Base_Elems[ Elem_p->Idx ].Data[ 0u ], pointer_to_real_data, size_of_real_data <= 256u);
    __atomic_store(&Shm_p>Snd_Cnt, &Tmp_Snd_Cnt, __ATOMIC_RELEASE);
}

Код считывателя:

Tmp_Rcv_Cnt = Shm_p->Rcv_Cnt;
__atomic_load(&Shm_p>Snd_Cnt, &Tmp_Snd_Cnt, __ATOMIC_ACQUIRE);
if (Tmp_Snd_Cnt != Tmp_Rcv_Cnt) {
    Elem_p = &Shm_p->Elems[ Tmp_Rcv_Cnt ];
    pointer_to_real_data = &Elem_p->Base_Elems[ Elem_p->Idx ].Data[ 0u ];
    Elem_p->Idx = (Elem_p->Idx + 1u) % 2u;  
    Tmp_Rcv_Cnt = (Tmp_Rcv_Cnt + 1u) % 100u;
    __atomic_store(&Shm_p>Rcv_Cnt, &Tmp_Rcv_Cnt, __ATOMIC_RELEASE);
}

Это должно работать ... верно?!

...