Странное поведение кругового буфера

Я реализовал кольцевой буфер без блокировки один производитель-один-потребитель. Эталонная реализация — та, что находится по адресу: http://home.comcast.net/~lang.dennis/code/ring/ring.html (Базовая реализация, первый листинг сверху).

РЕДАКТИРОВАТЬ: Для справки оригинальный код:

template <class T, size_t RingSize>
class RingBuffer
{
public:
RingBuffer(size_t size = 100)
: m_size(size), m_buffer(new T[size]), m_rIndex(0), m_wIndex(0)
{ assert(size > 1 && m_buffer != NULL); }

~RingBuffer()
{ delete [] m_buffer; };

size_t Next(size_t n) const
{ return (n+1)%m_size; }
bool Empty() const
{ return (m_rIndex == m_wIndex); }
bool Full() const
{ return (Next(m_wIndex) == m_rIndex); }

bool Put(const T& value)
{
if (Full())
return false;
m_buffer[m_wIndex] = value;
m_wIndex = Next(m_wIndex);
return true;
}

bool Get(T& value)
{
if (Empty())
return false;
value = m_buffer[m_rIndex];
m_rIndex = Next(m_rIndex);
return true;
}

private:
T*              m_buffer;
size_t          m_size;

// volatile is only used to keep compiler from placing values in registers.
// volatile does NOT make the index thread safe.
volatile size_t m_rIndex;
volatile size_t m_wIndex;
};

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

Для наглядности функция get:

bool Get(T& value)
{
size_t w=m_wIndex;
size_t r=m_rIndex;
if (Empty(w,r))
return false;
value = m_buffer[r];
//just in case the compiler decides to be extra smart
compilerbarrier();
m_rIndex = Next(r);
return true;
}

Затем я создал отдельные потоки производителя и потребителя:

Нить производителя

uint64_t i = 0;
while (i <= LOOPS) {
if (buf.put(i)) {
i += 1;
}
}
consumerthread.join(); //pseudocode: wait for the consumer to finish

Поток потребительской нити:

uint64_t i=0;
while (i < LOOPS) {
buf.get(i);
}

Producer помещает целые числа [0, LOOPS] в буфер, и потребитель получает их по одному из буфера, пока, наконец, не получит целочисленное значение LOOPS, и цикл потребителя не завершится. Обратите внимание, что размер буфера намного меньше, чем ПЕТЛИ.

Если я оставлю ключевое слово volatile для индексов чтения и записи, все будет работать как шарм. Цикл Consumer завершается, а производитель возвращается.

Но если я уберу ключевое слово volatile, потребитель никогда не вернется.

Как ни странно, этот потребительский цикл завершается:

uint64_t i=0;
while (i < LOOPS) {
buf.get(i);
fprintf(stderr,"%lu\n",i);
}

И этот тоже заканчивается:

uint64_t i=0, j=0;
while (j < LOOPS) {
if(buf.get(i)) {
j=i;
}
}

Что происходит? Я скомпилировал код, используя gcc 4.8.4 с установленным флагом -O3 на 64-битной машине intel (i3) под управлением Ubuntu.

2

Решение

Так как проблема возникает только при удалении volatileэта проблема, скорее всего, вызвана оптимизацией компилятора, которая оптимизирует чтение m_wIndex в потоке читателей. Компилятор считает, что он знает все, что может изменить это значение, поэтому нет смысла читать его из памяти более одного раза.

Добавление вызова к fprintf побеждает эту оптимизацию, потому что вполне возможно, что fprintf может изменить эту переменную.

Добавление j сделал ваш код более сложным, а также, кажется, победить оптимизацию.

3

Другие решения


По вопросам рекламы ammmcru@yandex.ru
Adblock
detector