Применение кольцевого буфера (ring buffer) - обычный прием, когда нужно организовать поток данных между асинхронными по отношению друг к другу процессами - например, между получением данных по каналу связи и обработкой этих данных в программе. Кольцевой буфер является упрощенным аналогом очереди (queue), которая применяется в многозадачных операционных системах (Windows, Linux, FreeRTOS и многих других) для организации безопасного (thread-safe) обмена данными между потоками (синхронизации).
Кольцевой буфер может использоваться не только на прием, но и на передачу - например, если нужно быстро подготовленные где-то данные постепенно передавать с течением времени. Кольцевой буфер удобен прежде всего тем, что очень просто производить заполнение буфера, проверку наличия данных в буфере и выборку данных из буфера, при этом не нужно особенно беспокоиться о выходе данных за границу буфера, переполнении памяти и т. п. Кольцевой буфер позволяет корректно обмениваться данными между обработчиком прерывания и основной программой.
Кольцевой буфер является разновидностью буфера FIFO, First Input First Output (первый зашел - первый вышел). Принцип кольцевого буфера довольно прост - в памяти выделяется непрерывный блок размером обычно равным степени двойки (назовем его buffer), и два индекса (idxIN и idxOUT) - один индекс указывает на место для записи в буфер (idxIN), другой - на место чтения из буфера. Размер буфера, равный степени двойки, выбирается для того, чтобы удобно было манипулировать индексами, указывающими на данные буфера, с помощью инкремента/декремента и наложения маски (это будет понятно далее). Индекс - это обычное число, равное адресу ячейки буфера. Например, на ячейку buffer[0] указывает индекс, равный нулю. Количество бит индекса как раз равно степени двойки размера буфера. Максимально удобны буфера размером 256 байт - в этом случае в качестве индекса можно применить 1 байт, и маску при операциях с указателями уже накладывать не надо. Код получается в этом случае максимально быстрый и компактный. На рисунке показан принцип работы кольцевого буфера на примере буфера в 16 байт (желтым показаны еще не обработанные данные буфера):
Вот так, например, выделяется буфер:
#define BUF_SIZE 16 //размер буфера обязательно должен быть равен степени двойки!
#define BUF_MASK (BUF_SIZE-1)
u8 idxIN, idxOUT;
char buffer [BUF_SIZE];
При помещении значения value в буфер используется индекс idxIN. Это делается так:
buffer[idxIN++] = value;
idxIN &= BUF_MASK;
Значение константы BUF_MASK равно размеру буфера минус 1 (при условии, что размер буфера равен степени двойки). При таком принципе работы с индексом происходит автоматический переход на начало буфера, как только мы достигли его конца.
Операция выборки из буфера происходит похожим образом, только используется индекс idxOUT:
value = buffer[idxOUT++];
idxOUT &= BUF_MASK;
Теперь становится понятным, почему при размере буфера 256 байт и байтовом индексе не нужна операция наложения маски - переход в ноль индекса при достижении конца буфера происходит автоматически.
Проверка на наличие данных в буфере происходит очень просто - если idxIN не равен idxOUT, то в буфере есть данные, в противном случае данных в буфере нет. Индекс idxOUT как-бы постоянно догоняет индекс idxIN при работе с данными буфера. Если догнал - значит, считывать из буфера больше нечего.
if (idxIN != idxOUT)
{
//данные есть, обрабатываем их
...
}
Сбросить данные буфера (т. е. удалить их оттуда) тоже очень просто - для этого в idxOUT записывают значение idxIN:
idxOUT = idxIN;
Иногда бывает необходимо знать, что не только данные в буфере есть, а еще нужно знать сколько именно байт данных в буфере. Для этого используется довольно простая процедура:
Кольцевой буфер не очень удобен для случаев, когда имеется несколько отправителей и один получатель, тогда при выборке информации из буфера необходимо правильно её сортировать. Для обратного случая - один отправитель и несколько получателей - кольцевой буфер подходит отлично, но нужно иметь для каждого получателя отдельный индекс idxOUT. Если же только один получатель и только один отправитель, то кольцевой буфер по быстродействию и простоте реализации практически идеальный вариант.
[RingBuffer_t, автор Stanimir Bonev]
Есть более сложные, однако более гибкие в плане удобства использования кольцевые буферы. Реализация программного менеджера кольцевого буфера от Stanimir Bonev позволяет работать с кольцевым буфером как с линейным блоком памяти (можно выбрать как один байт, так и несколько). Есть возможность произвольно выбирать размер буфера, реализована блокировка на исключительный доступ к буферу.
Работа с буфером происходит через структуру RingBuffer_t:
typedefstruct _RingBuffer_t
{
u8 *pPush; // Текущая позиция вставки
u8 *pPop; // Текущая позиция выборки
u32 Count; // Количество занятых байт в буфере
u8 *pBegin; // Начало кольцевого буфера в памяти
u8 *pEnd; // Конец кольцевого буфера в памяти
u32 Size; // Размер кольцевого буфераbool LockUsed; // Флаг блокировки на извлечение из буфераbool LockFree; // Флаг блокировки на вставку в буфер
} RingBuffer_t, *pRingBuffer_t;
В таблице приведено описание функций кольцевого буфера.
Функция
Описание
RingBuff_Init
Инициализация структуры кольцевого буфера.
RingBuff_GetAvableBufferSize
Получает сведения о доступном месте в кольцевом буфере и позиции в памяти, куда можно записать данные. Если буфер заблокирован, то функция вернет 0.
RingBuff_Push
Функция записывает данные в буфер. Перед её вызовом обязательно должна быть вызвана функция RingBuff_GetFreeLinBuff.
RingBuff_Pop
Функция для выборки данных из буфера.
RingBuff_GetFreeLinBuff
Получает сведения о доступном месте в кольцевом буфере и позиции в памяти, куда можно записать данные. Функция должна вызываться непосредственно перед записью в буфер, она одновременно устанавливает блокировку на запись.
RingBuff_GetUsedLinBuff
Функция для выборки данных из буфера. Возвратит указатель на еще не выбранные данные и их размер.
RingBuff_Allocate
Резервирует линейный блок в буфере. Эта функция должна быть вызвана после вставки в буфер, она одновременно разблокирует буфер на запись.
RingBuff_Free
Функция освобождает блок в буфере после выборки данных. Она одновременно снимает блокировку буфера на чтение.
Пример использования этих функций можно увидеть в проектах IAR для платы Olimex STM32-103STK, также см. врезку ниже.
// Разрешение счета таймера:
TIM_Cmd(TIM1,ENABLE);
// CDC USB
UsbCdcInit();
// Разрешение активизации pull-up резистора:
USB_ConnectRes(TRUE);
// Инициализация кольцевых буферов приема и передачи:RingBuff_Init(&RXUSBringBuffer,RxBuff,RING_BUFFER_SIZE);RingBuff_Init(&TXUSBringBuffer,TxBuff,RING_BUFFER_SIZE);
EXT_CRT_SECTION();
msg[0] =0;
while(1)
{
if (IsUsbCdcConfigure())
{
// Обработка приема USB: принятые данные помещаются в буфер RXUSBringBuffer,
// если там есть свободное место.// Получение свободного места в буфере приема:
pBufferDst =RingBuff_GetFreeLinBuff(&RXUSBringBuffer,&SizeDst);
if(SizeDst)
{
// Чтение данных из USB:
SizeSrc = UsbCdcRead(pBufferDst,SizeDst);
if(SizeSrc)
{
LED1(ON);
}
}
RingBuff_Allocate(&RXUSBringBuffer,SizeSrc);
// Обработка передачи USB: если в буфере передачи TXUSBringBuffer есть данные,
// то они передаются через USB, и эти данные удаляются из буфера.// Получение заполненного места в буфере передачи:
pBufferSrc =RingBuff_GetUsedLinBuff(&TXUSBringBuffer,&SizeSrc);
if(SizeSrc)
{
// Отправка данных:
LED2(ON);
UsbCdcWrite(pBufferSrc,SizeSrc);
RingBuff_Free(&TXUSBringBuffer,SizeSrc);
}
// Выборка данных из буфера приема и отправка их в буфер передачи
pBufferSrc =RingBuff_GetUsedLinBuff(&RXUSBringBuffer,&SizeSrc);
if (SizeSrc)
{
u32 portion = Push(&TXUSBringBuffer, pBufferSrc, SizeSrc);
RingBuff_Free(&RXUSBringBuffer,portion);
}
if (PRESSED(S2_PIN))
{
strcat(msg, "Нажата S2\r\n");
}
if (PRESSED(S3_PIN))
{
strcat(msg, "Нажата S3\r\n");
}
if (msg[0])
{
Push(&TXUSBringBuffer, msg, strlen(msg));
msg[0] =0;
}
LED1(OFF);
LED2(OFF);
}
}
}
[RingBuffer_t из библиотеки LUFA]
Еще одну реализацию кольцевого буфера см. в библиотеке LUFA, файл LUFA\Drivers\Misc\RingBuffer.h, автор Dean Camera. Эта реализация намного проще, чем предыдущая, но имеет определенные ограничения.
Работа с буфером происходит через структуру RingBuffer_t:
typedefstruct
{
u8* In; // Текущее место сохранения в кольцевом буфере.
u8* Out; // Текущее место выборки из кольцевого буфера.
u8* Start; // Указатель на начало нижележащего хранилища буфера.
u8* End; // Указатель на конец нижележащего хранилища буфера.
u16 Size; // Размер нижележащего хранилища буфера.
u16 Count; // Количество байт, сохраненных в настоящее время в буфере.
}RingBuffer_t;
В таблице приведено описание функций кольцевого буфера.
Функция
Описание
RingBuffer_InitBuffer
Инициализирует кольцевой буфер, чтобы его можно было использовать. Буферы должны быть инициализированы этой функцией перед выполнением любой операции над буфером. Уже инициализированные буферы могут быть сброшены повторной переинициализацией путем вызова этой функции.
RingBuffer_GetCount
Запрашивает текущее количество байт, сохраненных в кольцевом буфере. Это значение вычисляется путем входа в атомарную блокировку буфера, так что буфер не может модифицироваться, пока происходят вычисления. Возвращенное значение должно быть закэшировано, когда вычитывается содержимое буфера, поэтому на атомарную блокировку может быть потрачено некоторое малое время.
RingBuffer_GetFreeCount
Запрашивает свободное пространство в определенном буфере. Это значение может быть вычислено путем входа в атомарную блокировку на буфере, чтобы буфер не был случайно изменен во время проведения вычислений объема свободного места.
RingBuffer_IsEmpty
Атомарно определяет, есть ли в указанном кольцевом буфере какие-либо данные. Эта проверка должна быть сделана перед удалением данных из буфера, чтобы гарантировать отсутствие опустошения буфера (отсутствие underflow, т. е. отсутствие ошибочного чтения из пустого буфера).
RingBuffer_IsFull
Атомарно определяет, есть ли в указанном кольцевом буфере какое-либо свободное место. Это должно быть проверено перед сохранением данных в буфер для гарантии, что данные не будут потеряны из-за переполнения буфера (overrun, т. е. ошибочная попытка записи в полностью заполненный буфер).
RingBuffer_Insert
Вставляет элементы в кольцевой буфер.
RingBuffer_Remove
Удаляет (извлекает) элемент из кольцевого буфера.
RingBuffer_Peek
Возвратит следующий элемент, сохраненный в кольцевом буфере, без его удаления.
// Вставка некоторых данных в буфер:
RingBuffer_Insert(&Buffer, 'H');
RingBuffer_Insert(&Buffer, 'E');
RingBuffer_Insert(&Buffer, 'L');
RingBuffer_Insert(&Buffer, 'L');
RingBuffer_Insert(&Buffer, 'O');
// Получение количества сохраненных в буфере байт:
u16 BufferCount = RingBuffer_GetCount(&Buffer);
// Вывод на печать длины сохраненных данных:
printf("Buffer Length: %d, Buffer Data: \r\n", BufferCount);
// Вывод на печать содержимого буфера по одному символу:
while (BufferCount--)
putc(RingBuffer_Remove(&Buffer));
[Другие популярные разновидности буферов]
Флажковый буфер. Работает он как набор одинаковых ячеек в памяти. Каждая ячейка может иметь как простую структуру, так и сложную (в виде структуры или объекта). Каждая ячейка снабжена флажком, показывающим - занята эта ячейка или свободна. Флажковый буфер удобен в двух случаях. Во-первых, когда у передаваемой информации есть один отправитель и несколько получателей (и наоборот - когда получатель один, а отправителей несколько). Во-вторых, когда в нескольких ячейках может содержаться части какого-то большого сообщения, и эти части могут прийти в разное время, и не по порядку. Флаги занятости ячеек обычно для удобства держат в отдельном регистре. Если регистр 8-битный, то он может поддерживать максимум 8 ячеек, если 16-битный, то 16 ячеек, и так далее.
Алгоритм заполнения флажкового буфера и выборки из него следующий. Когда отправителю нужно поместить в буфер сообщение, то он ищет в регистре флагов первый попавшийся нулевой бит. Когда нулевой бит найден, то передатчик устанавливает его в единицу (помечая тем самым занятой соответствующую ячейку), вычисляет по номеру бита индекс ячейки, и затем помещает в ячейку информацию. Получатель действует так - просматривает флаги в поиске ненулевых бит. Все ячейки, которым соответствуют ненулевые биты, опрашиваются, и если там находится подлежащая выборке информация, то эта информация копируется из ячейки буфера, и соответствующий флажок сбрасывается, сигнализируя об освобождении ячейки.
Кэширование. Этот буфер работает как набор пронумерованных ячеек, составляющих образ какой-то другой (обычно более медленной) памяти, условно назовем эту память внешнее хранилище. В каждой ячейке хранятся как полезные данные, так и адрес, который соответствует месту размещения информации во внешнем хранилище. Обычный способ применения такого кэширования - доступ к данным на диске (карта SD, жесткий диск, флешка и т. п.), в этом случае каждая ячейка часто имеет размер 512 байт (сектор), и номер сектора является её адресом.
Стек. Если кольцевой буфер является буфером типа FIFO, то стек в этом отношении является его полной противоположностью, так как стек имеет тип FILO (First Input Last Output, т. е. первым пришел и последним вышел). Стек обычно реализован на уровне системы (аппаратно в микроконтроллере или процессоре), и поддерживается как со стороны инструкций процессора, так и со стороны системы программирования. Этот стек программист часто может использовать только неявно - определяя локальные переменные в функциях, передавая параметры и возвращая значения.
Программист сам может реализовать подпрограммы для доступа к своему собственному буферу со стековой структурой - если это требуется для обеспечения функционала программы. Однако искусственный стековый буфер применяется достаточно редко.
Рассмотрим буфер на 512 1-байтовых элементов . Пусть хвост =11 (позиция откуда читаем), голова =1 (позиция куда пишем). Очевидно что в этом случае количество элементов в буфере будет: (голова-хвост) &511 =502 элемента. При обратной ситуации (хвост=1, голова=11) (11-1)& 511 =10 элементов. Что лучше подходит чем формула в статье с маркировкой UPD100920.
Можно избежать пересечения индексов начала и конца при записи, проверяя заполненность буфера перед записью по условию: (голова-хвост)% (размер-1) == sizeof(буфер)-1. Т. е. если хвост на 0, голова на 511,исходя из алгоритма записи в статье - в буфере 511 байт. Таким способом мы жертвуем 1 байтом при сравнении выше, но избегаем ситуации пересечения головы и хвоста при записи.
Различить ситуации, когда буфер пуст и полон при idxIN==idxOUT, можно простым и элегантным способом. Нужно использовать индексы буфера вдвое большего размера, и все.
Для удобства в примере заменил прединкремент постинкрементом.
Давайте разделять тип применяемого буфера и методы определения достоверности сообщений. Переполнить можно ЛЮБОЙ буфер. Это никак не меняет его принцип. Если писать в быстрее, чем читать - проблема не в типе буфера, а в некорректном проектировании протокола обмена, или недостаточной скорости обработки.
В конкретной ситуации, когда буфер заполнен и сейчас, вот, должны сравняться хвост и голова - я ввожу проверку перед записью. Если в протоколе предусмотрена работа с коллизиями - производитель данных получит ошибку на последний отосланный пакет.
Как он дальше будет с этим жить - не относится к типу буфера вообще. от слова совсем.
P.S. Можете применить шину CAN и забирать сообщения только тогда, когда буфер способен их проглотить. Всё остальное CAN сделает за вас на аппаратном уровне. он будет обрабатывать ошибку переполнения буфера драйвера и долбить сообщение аж пока не передаст его.
Ситуация, когда буфер кольцевой буфер заполнен полностью никак не отличается от ситуации, когда он пуст, следовательно не будет работать, если читать медленнее, чем писать, собственно об этом Вы уже написали. Вопрос в том, как сделать, чтобы ситуации различались? Придется вводить флаги и при каждой записи и чтении сперва проверять флаги, потом выставлять?
microsin: достоинство кольцевого буфера именно в его простоте и скорости. Единственное условие, которое нужно выполнить, чтобы такой буфер работал - не допускать его переполнения, т. е. делать выборку значений быстрее, чем значения записываются в буфер. Если нужно что-то кроме этого функционала, то это уже не будет кольцевой буфер, а что-то другое (FIFO, стек, очередь и т. д.), что не имеет смысла здесь обсуждать.
А если 256 ячеек мало, и нужно индексы использовать 2-х байтовые, то тогда как будет выглядеть организация такого буфера?
microsin: абсолютно так же, на принцип работы кольцевого буфера это никак не повлияет. Помните только, что выгоднее всего использовать для размера буфера число в степени N двойки (размер буфера = 2^N). Для организации закольцовывания индекса при инкременте достаточно накладывать на индекс маску, равную (2^N)-1.
Не совсем верно. Кольцевой буфер используется в классической задаче "производителя"-"потребителя" (см. например здесь: http://www.cs.mtu.edu/~shene/NSF-3/e-Book/MONITOR/ProducerConsumer-1/MON-example-buffer-1.html). При переполнении буфера производитель должен быть заблокирован, разблокироваться он может по сигналу от потребителя, когда последний извлечет данные из буфера. Недостоверными данные буфера при переполнении и последующем прекращении заполнения станут только в одном случае: если буфер обязан содержать чисто реалтаймовые данные: скажем, измерения за BUF_SIZE моментов времени вплоть до текущего момента. Но тогда задача еще проще: можно иметь один индекс для записи и считывать весь буфер целиком при необходимости обработки. Индекс записи в таком случае покажет границу между самыми новыми и самыми старыми данными.
microsin: при обмене с квитированием, т. е. с блокировкой/разблокировкой передатчика данных (с управлением потоком данных от потребителя) кольцевой буфер может быть не нужен, так как всегда гарантируется, что приемник готов принять данные в нужное время. Повторяю еще раз, на всякий случай - в этой статье подобная ситуация с блокировкой/разблокировкой передатчика данных - НЕ РАССМАТРИВАЕТСЯ.
Если буфер заполнится до конца, то индексы idxIN и idxOUT сравняются. И тогда функция idxDiff() вернет не BUF_SIZE, а 0. Не говоря уже о том, что u8 idxDiff (u8 idxIN, u8 idxOUT) в принципе не может вернуть BUF_SIZE, равный 256... То же самое относится и к фразе "Если догнал - значит, считывать из буфера больше нечего". На самом деле, или нечего, или буфер заполнен до конца. Если принять за постулат, что буфер опустошается всегда быстрее, чем заполняется, то это не важно. Но что если что-то пойдет не так, как мы предполагаем? Скажем, потеряна связь с потребителем данных, символы не извлекаются из кольца, а производитель продолжает писать данные: средств для идентификации переполнения ведь нет в принципе! За границу мы, конечно, не выйдем, но вот последовательность чтения накопленных данных будет искажена.
microsin: Вы во всем правы, и об этом в статье явно не пишется только потому, что буфер КОЛЬЦЕВОЙ, и если запись в буфер будет идти быстрее чтения, то буфер переполнится, и данные в нем будут недостоверны. В этом случае не только нельзя применить кольцевой буфер, но и невозможно синхронизировать процессы по передаваемым данным, если не отбрасывать пакеты. Описание такого поведения программы выходит за рамки статьи, статья только про принцип работы с КОЛЬЦЕВЫМ БУФЕРОМ. Повторяю снова - говорить о кольцевом буфере, когда скорость входящего потока данных превышает скорость исходящего - совершенно бессмысленно.
Ранее придумал нечто похожее на Ваш пример, только у меня было 2 строки char и два указателя. Один указывал на строку приема, второй - на строку, которую надо обработать. После некоторой возни с синхронизацией этих потоков все работало достаточно шустро :)
microsin: вся прелесть кольцевого буфера в том, что про синхронизацию можно вообще забыть, как про страшный сон. При работе с кольцевым буфером нужно только соблюсти два условия - средняя скорость записи в буфер должна быть меньше средней скорости чтения из него, и размер буфера должен быть достаточно большим для того, чтобы не терялись записываемые за один раз (до операции чтения) данные.
Как лучше контролировать корректность записываемых данных, то есть случай, когда записывается больше данных, чем осталось места в буфере.
microsin: никак, потому что кольцевой буфер не предназначен для такого случая. Использование кольцевого буфера автоматически подразумевает условие, что СРЕДНЯЯ скорость записи в кольцевой буфер ВСЕГДА МЕНЬШЕ СРЕДНЕЙ скорости чтения из кольцевого буфера.
Комментарии
Можно избежать пересечения индексов начала и конца при записи, проверяя заполненность буфера перед записью по условию: (голова-хвост)% (размер-1) == sizeof(буфер)-1. Т. е. если хвост на 0, голова на 511,исходя из алгоритма записи в статье - в буфере 511 байт. Таким способом мы жертвуем 1 байтом при сравнении выше, но избегаем ситуации пересечения головы и хвоста при записи.
Для удобства в примере заменил прединкремент постинкрементом .
#define BUF_MASK0 (BUF_SIZE-1)
#define BUF_MASK1 (2*BUF_SIZE-1)
buffer[idxIN & BUF_MASK0] = value;
idxIN++ &= BUF_MASK1;
value = buffer[idxOUT & BUF_MASK0];
idxOUT++ &= BUF_MASK1;
функция idxDiff вернет правильное значение при полном буфере.
В конкретной ситуации, когда буфер заполнен и сейчас, вот, должны сравняться хвост и голова - я ввожу проверку перед записью. Если в протоколе предусмотрена работа с коллизиями - производитель данных получит ошибку на последний отосланный пакет.
Как он дальше будет с этим жить - не относится к типу буфера вообще. от слова совсем.
P.S. Можете применить шину CAN и забирать сообщения только тогда, когда буфер способен их проглотить. Всё остальное CAN сделает за вас на аппаратном уровне. он будет обрабатывать ошибку переполнения буфера драйвера и долбить сообщение аж пока не передаст его.
microsin: достоинство кольцевого буфера именно в его простоте и скорости. Единственное условие, которое нужно выполнить, чтобы такой буфер работал - не допускать его переполнения, т. е. делать выборку значений быстрее, чем значения записываются в буфер. Если нужно что-то кроме этого функционала, то это уже не будет кольцевой буфер, а что-то другое (FIFO, стек, очередь и т. д.), что не имеет смысла здесь обсуждать.
microsin: абсолютно так же, на принцип работы кольцевого буфера это никак не повлияет. Помните только, что выгоднее всего использовать для размера буфера число в степени N двойки (размер буфера = 2^N). Для организации закольцовывания индекса при инкременте достаточно накладывать на индекс маску, равную (2^N)-1.
Совсем забыть? И для индексов volatile не нужно?
Недостоверными данные буфера при переполнении и последующем прекращении заполнения станут только в одном случае: если буфер обязан содержать чисто реалтаймовые данные: скажем, измерения за BUF_SIZE моментов времени вплоть до текущего момента. Но тогда задача еще проще: можно иметь один индекс для записи и считывать весь буфер целиком при необходимости обработки. Индекс записи в таком случае покажет границу между самыми новыми и самыми старыми данными.
microsin: при обмене с квитированием, т. е. с блокировкой/разблокировкой передатчика данных (с управлением потоком данных от потребителя) кольцевой буфер может быть не нужен, так как всегда гарантируется, что приемник готов принять данные в нужное время. Повторяю еще раз, на всякий случай - в этой статье подобная ситуация с блокировкой/разблокировкой передатчика данных - НЕ РАССМАТРИВАЕТСЯ .
То же самое относится и к фразе "Если догнал - значит, считывать из буфера больше нечего". На самом деле, или нечего, или буфер заполнен до конца.
Если принять за постулат, что буфер опустошается всегда быстрее, чем заполняется, то это не важно. Но что если что-то пойдет не так, как мы предполагаем? Скажем, потеряна связь с потребителем данных, символы не извлекаются из кольца, а производитель продолжает писать данные: средств для идентификации переполнения ведь нет в принципе! За границу мы, конечно, не выйдем, но вот последовательно сть чтения накопленных данных будет искажена.
microsin: Вы во всем правы, и об этом в статье явно не пишется только потому, что буфер КОЛЬЦЕВОЙ, и если запись в буфер будет идти быстрее чтения, то буфер переполнится, и данные в нем будут недостоверны. В этом случае не только нельзя применить кольцевой буфер, но и невозможно синхронизироват ь процессы по передаваемым данным, если не отбрасывать пакеты. Описание такого поведения программы выходит за рамки статьи, статья только про принцип работы с КОЛЬЦЕВЫМ БУФЕРОМ. Повторяю снова - говорить о кольцевом буфере, когда скорость входящего потока данных превышает скорость исходящего - совершенно бессмысленно.
microsin: вся прелесть кольцевого буфера в том, что про синхронизацию можно вообще забыть, как про страшный сон. При работе с кольцевым буфером нужно только соблюсти два условия - средняя скорость записи в буфер должна быть меньше средней скорости чтения из него, и размер буфера должен быть достаточно большим для того, чтобы не терялись записываемые за один раз (до операции чтения) данные.
microsin: никак, потому что кольцевой буфер не предназначен для такого случая. Использование кольцевого буфера автоматически подразумевает условие, что СРЕДНЯЯ скорость записи в кольцевой буфер ВСЕГДА МЕНЬШЕ СРЕДНЕЙ скорости чтения из кольцевого буфера.
RSS лента комментариев этой записи