Короткая версия: Проблема заключается в том, что не реализована третья вещь, которую вы сказали, что сделали («указатель чтения потребителя <указатель записи производителя» и «производитель не будет переопределять элементы, которые есть у потребителя)читаю ").В частности, не проверять перезапись проблематично.Хороший план, не очень хорошее исполнение. </p>
Подробнее: Вы никогда не проверяете, заполнен ли круговой массив.Поскольку ваш тестовый сценарий находится на границе между массивом, который заполнен, и массивом переполненным, вы в конечном итоге столкнетесь с состоянием гонки.Иногда производитель перезаписывает начало массива до того, как его прочитает потребитель.
Вот временная шкала (измеряется в секундах):
0: Производитель записывает 5 значений вмассив.
1: Producer записывает в массив 6-е значение.
2: Producer записывает в массив 7-е значение.
3: Producer записывает в массив 8-е значение.
4: Producer записывает в массив 9-е значение.
5: Producer записывает 10-е значение вмассив (теперь он заполнен), и потребитель начинает свой цикл (но далеко не уходит, поскольку первый шаг в каждой итерации спит секунду).
5 + n: Producer записывает значение в массив, а Consumer читает значение из массива.Если источник идет первым, размер массива временно увеличивается до 11, что превышает его емкость.
Посмотрите на места, где Потребитель читает что-то отличное от того, что написал Продюсер.Сравните то, что прочитал Потребитель, с тем, что Продюсер написал 10 шагов спустя.
Теперь для общей критики, которую никто не спрашивал.
Есть два аспекта вашей реализации кругового массива, которые выглядят странно/неправильно.Во-первых, существует дублирование хранилища, в котором вы поддерживаете два идентичных указателя начала и два идентичных указателя конца-конца.Во-вторых, похоже, что _size
всегда 0
, что кажется бесполезным.
Один аспект, который не обязательно неправильный, но может быть неправдоподобным, - это то, как вы указываете N
.Рассматривали ли вы сделать N
параметром шаблона, похожим на то, что было сделано для std::array
?Это может уменьшить объем используемой памяти (не нужно хранить _capacity
) и устранить необходимость динамического управления памятью.
Приложение:
Мне пришло в голову, что у вас есть данныеэлементы, которые не должны быть изменены после создания, но которые не помечены const
.Возможно, вы захотите решить эту проблему, тем более что пометка их const
прояснит, что они не могут быть вовлечены в состояние гонки.Таким образом, вы могли бы объявить члены данных циклического массива более похожими на:
private:
int const _capacity;
T * const start;
T * const past_end;
T* head;
T* tail;
Тогда конструктор мог бы быть больше похож на:
CircularArray(int N = 10) :
_capacity(N),
start(new T[N]),
past_end(start + N)
{
head = tail = start;
}
(На самом деле, вы можете использовать список инициализатора для всехчлены; я просто думал, что небольшие изменения будут более удобочитаемыми.) Еще одно преимущество стиля в том, что оба параметра new
и delete
будут применяться к start
, что выглядит лучше для тех, кто проверяет код.
Еще одним упрощением может быть использование выражения start + N
везде, где вы использовали past_end
.Разница в производительности должна быть незначительной, и вы уменьшите объем используемой памяти.
В качестве альтернативы, мое раннее предложение сделать N
параметром шаблона сделало бы вопрос const
спорным.Используя N в качестве параметра шаблона (все еще со значением по умолчанию 10), члены данных циклического массива могут быть просто:
private:
T start[N]; // As a template parameter, N is a compile-time constant
T* head; // Or an index into the array
T* tail; // Or an index into the array
Переключение на индексы также дает больше оснований отбрасывать элемент конца строки,Индекс конца-конца становится N
, который является постоянной времени компиляции - нет необходимости тратить на него место для хранения.