Criando um Buffer Circular em C e c++

Updated: 20210321

devido à natureza limitada de recursos dos sistemas embarcados, estruturas de dados de buffer circulares podem ser encontradas na maioria dos projetos.buffers circulares (também conhecidos como buffers de anel) são buffers de tamanho fixo que funcionam como se a memória fosse contígua & de natureza circular. Como a memória é gerada e consumida, os dados não precisam ser remexidos – em vez disso, os ponteiros cabeça/cauda são ajustados. Quando os dados são adicionados, o ponteiro da cabeça avança. Quando os dados são consumidos, o ponteiro da cauda avança. Se você chegar ao fim do buffer, os ponteiros simplesmente enrolam em torno do início.

para um resumo mais detalhado da operação de tampão circular, por favor consulte o artigo do Wikipedia. O resto do artigo assume que você tem uma compreensão de como os amortecedores circulares funcionam.

Índice:

  1. porquê utilizar um tampão Circular?
  2. C Implementação
    1. Usando Encapsulamento
    2. Design de API
    3. Determinar se um Buffer está Cheio
    4. Buffer Circular Tipo de Contentor
    5. Implantação
    6. de Uso
    7. Modificações para Remover o full sinalizador
  3. Implementação C++
    1. Definição de Classe
    2. Implantação
    3. de Uso
    4. Atualizações para C++17
  4. Colocá-Lo Todos Juntos
  5. Ler Mais

Por que Usar Um Buffer Circular?os buffers circulares são frequentemente usados como filas de tamanho fixo. O tamanho fixo é benéfico para sistemas embutidos, como desenvolvedores muitas vezes tentam usar métodos estáticos de armazenamento de dados em vez de alocações dinâmicas.os amortecedores circulares de são também estruturas úteis para situações em que a produção e o consumo de dados ocorrem a taxas diferentes: os dados mais recentes estão sempre disponíveis. Se o consumidor não conseguir acompanhar a produção, os dados obsoletos serão substituídos por dados mais recentes. Usando um buffer circular, podemos garantir que estamos sempre consumindo os dados mais recentes.

para casos de Utilização Adicionais, verifique os conceitos básicos do tampão interlaboratorial em Embedded.com.

C implementação

vamos começar com uma implementação C, como isso nos expõe a alguns dos desafios de design e tradeoffs ao criar uma biblioteca de buffer circular.

usando encapsulação

Uma vez que estamos a criar uma biblioteca de buffer circular, queremos ter a certeza que os utilizadores trabalham com as nossas APIs de biblioteca em vez de modificar a estrutura directamente. Nós também queremos manter a implementação contida em nossa biblioteca para que possamos alterá-la conforme necessário, sem exigir que os usuários finais atualizem seu código. O Usuário não precisa saber quaisquer detalhes sobre nossa estrutura, apenas que ela existe.

no cabeçalho da nossa biblioteca, iremos declarar a estrutura:

// Opaque circular buffer structuretypedef struct circular_buf_t circular_buf_t;

não queremos que os utilizadores trabalhem com umcircular_but_t pointer directamente, pois podem ter a impressão de que podem dereferir o valor. Vamos criar um tipo de punho que eles podem usar em vez disso.

a abordagem mais simples para o nosso punho é para typedef ocbuf_handle_t como ponteiro para o buffer circular. Isso nos impedirá de lançar o ponteiro dentro de nossa implementação da função.

// Handle type, the way users interact with the APItypedef circular_buf_t* cbuf_handle_t;

Uma abordagem alternativa seria fazer o identificador de uma uintptr_t ou void* valor. Dentro de nossa interface, nós lidaríamos com a tradução para o tipo de ponteiro apropriado. Mantemos o tipo de buffer circular escondido dos usuários, e a única maneira de interagir com os dados é através do manípulo.

Nós Vamos ficar com a implementação simples para manter o nosso código de exemplo simples e simples.

Design da API

Em Primeiro Lugar, devemos pensar em como os utilizadores irão interagir com um buffer circular:

  • Que precisa para inicializar o buffer circular recipiente com um buffer de tamanho e
  • Eles precisam destruir um buffer circular recipiente
  • Eles precisam repor o buffer circular recipiente
  • que Eles precisam para ser capaz de adicionar dados para o buffer
  • que Eles precisam para ser capaz de obter o próximo valor do buffer
  • Eles precisam de saber se o buffer está cheio ou vazio
  • o que Eles precisam saber o número de elementos no buffer
  • Eles precisam saber a capacidade máxima da memória intermédia

Usando essa lista, podemos montar uma API para a nossa biblioteca. Os usuários irão interagir com a biblioteca de buffer circular usando o nosso tipo de punho opaco, que é criado durante a inicialização.

I have chosenuint8_t as the underlying data type in this implementation. Você pode usar qualquer tipo particular que você gosta – apenas tenha cuidado para lidar com o buffer subjacente e número de bytes adequadamente.

/// Pass in a storage buffer and size /// Returns a circular buffer handlecbuf_handle_t circular_buf_init(uint8_t* buffer, size_t size);/// Free a circular buffer structure./// Does not free data buffer; owner is responsible for thatvoid circular_buf_free(cbuf_handle_t cbuf);/// Reset the circular buffer to empty, head == tailvoid circular_buf_reset(cbuf_handle_t cbuf);/// Put version 1 continues to add data if the buffer is full/// Old data is overwrittenvoid circular_buf_put(cbuf_handle_t cbuf, uint8_t data);/// Put Version 2 rejects new data if the buffer is full/// Returns 0 on success, -1 if buffer is fullint circular_buf_put2(cbuf_handle_t cbuf, uint8_t data);/// Retrieve a value from the buffer/// Returns 0 on success, -1 if the buffer is emptyint circular_buf_get(cbuf_handle_t cbuf, uint8_t * data);/// Returns true if the buffer is emptybool circular_buf_empty(cbuf_handle_t cbuf);/// Returns true if the buffer is fullbool circular_buf_full(cbuf_handle_t cbuf);/// Returns the maximum capacity of the buffersize_t circular_buf_capacity(cbuf_handle_t cbuf);/// Returns the current number of elements in the buffersize_t circular_buf_size(cbuf_handle_t cbuf);

determinando se um Buffer está cheio

Antes de prosseguirmos, devemos tomar um momento para discutir o método que vamos usar para determinar se ou buffer está cheio ou vazio.

ambos os casos” completos” e”vazios”do buffer circular parecem os mesmos: head e tail pointer são iguais. Existem duas abordagens para a diferenciação entre o cheio e o vazio:

  1. “perder” um slot no buffer:
    • Cheio de estado head + 1 == tail
    • o estado Vazio é head == tail
  2. Use bool bandeira e lógica adicional para diferenciar os estados::
    • Cheio de estado full
    • o estado Vazio é (head == tail) && !full

também devemos considerar a segurança do thread. Usando uma única célula vazia para detectar o caso “completo”, podemos suportar um único produtor e consumidor sem um bloqueio (desde que put e get não modifique as mesmas variáveis). A fila é thread-safe porque o produtor só irá modificar o índice head, e o consumidor só irá modificar o índicetail. Embora qualquer um dos índices possa estar ligeiramente desactualizado num dado contexto, isto não irá afectar a segurança do road da fila. Usando a bandeirafull, no entanto, cria um requisito para a exclusão mútua. Isto porque a bandeirafull é compartilhada tanto pelo produtor quanto pelo consumidor.é claro que a decisão tem as suas vantagens. Se o seu elemento buffer tem uma grande pegada de memória (como um buffer que é dimensionado para uma câmera i-frame), desperdiçar um slot pode não ser razoável em seu sistema. Se você tem vários produtores / consumidores interagindo com uma fila, você vai precisar de uma fechadura de qualquer maneira, então desperdiçar um slot não faz sentido. Se você não tem exclusão mútua disponível (por exemplo, porque você não está usando um SO), mas você está usando interrupções, então você vai querer usar a versão não-full flag. O modelo de memória usado em seu sistema também pode ter um impacto em sua decisão de ir sem um bloqueio.

a implementação abaixo usa a bandeira bool. Usar a bandeira requer uma lógica adicional nas rotinasget e put para atualizar a bandeira. Vou também descrever como fazer as modificações para um único produtor / consumidor que não usa o full flag.

Contentor de Buffer Circular Tipo

Agora que temos uma compreensão sobre as operações que precisamos de suportar, podemos desenhar o nosso Contentor de buffer circular.

usamos a estrutura do recipiente para gerir o estado do buffer. Para preservar a encapsulação, a estrutura do recipiente é definida dentro da nossa biblioteca .c file, em vez de no cabeçalho.

Vamos a necessidade de acompanhar o:

  • subjacente buffer de dados
  • tamanho máximo do buffer
  • O atual “cabeça” posição (incrementado quando os elementos são adicionados)
  • O atual “cauda” (incrementado quando os elementos são removidos)
  • Um sinalizador que indica se o buffer está cheio ou não
// The hidden definition of our circular buffer structurestruct circular_buf_t {uint8_t * buffer;size_t head;size_t tail;size_t max; //of the bufferbool full;};

Agora que o nosso container é projetado, estamos prontos para implementar as funções de biblioteca.

implementação

um detalhe importante a notar é que cada uma das nossas APIs requer uma pega de buffer inicializada. Em vez de sujar o nosso código com declarações condicionais, vamos utilizar afirmações para impor os nossos requisitos API no estilo “Design by Contract”.

Se as interfaces forem utilizadas indevidamente, o programa falhará imediatamente em vez de exigir que o usuário verifique e maneje o código de erro.por exemplo:

circular_buf_reset(NULL);

produz:

=== C Circular Buffer Check ===Assertion failed: (cbuf), function circular_buf_reset, file ../../circular_buffer.c, line 35.Abort trap: 6

outra nota importante é que a implementação mostrada abaixo não é segura por thread. Não foram adicionadas fechaduras à biblioteca de buffer circular subjacente.

Inicializar e reiniciar

vamos começar no início: inicializando um buffer circular. A nossa API tem clientes que fornecem o buffer subjacente e o tamanho do buffer, e nós devolvemos-lhes um buffer circular. A razão pela qual queremos que nossos usuários forneçam o buffer é que isso permite um buffer estaticamente alocado. Se nossa API criou o buffer sob o capô, precisaríamos fazer uso de alocação de memória dinâmica, que é muitas vezes proibida em programas de sistemas embutidos.

somos obrigados a fornecer uma instância circular de estrutura de buffer dentro da biblioteca para que possamos retornar um ponteiro para o usuário. I have used malloc for simplicity. Sistemas que não podem usar memória dinâmica simplesmente precisam modificar a função init para usar um método diferente, como a alocação de um conjunto estático de estruturas de buffer circulares pré-alocadas.

outra abordagem seria quebrar a encapsulação, permitindo que os usuários declarassem estaticamente estruturas de contêineres de buffer circulares fora da biblioteca. Neste caso, circular_buf_init precisa ser atualizado para tomar um ponteiro de estrutura. Nós também poderíamos ter nossa função init criar uma estrutura de contêiner na pilha e devolvê-la por atacado. No entanto, uma vez que a encapsulação está quebrada, os usuários serão capazes de modificar a estrutura sem usar as rotinas da biblioteca. Se você quiser preservar a encapsulação, você precisa trabalhar com ponteiros em vez de instâncias de estrutura concreta.

// User provides structvoid circular_buf_init(circular_buf_t* cbuf, uint8_t* buffer, size_t size);// Return a concrete structcircular_buf_t circular_buf_init(uint8_t* buffer, size_t size);// Return a pointer to a struct instance - preferred approachcbuf_handle_t circular_buf_init(uint8_t* buffer, size_t size);

estaremos retornando um manípulo para uma estrutura que é alocada dentro da biblioteca. Uma vez que criamos nosso container, precisamos povoar os valores e chamar reset nele. Antes de retornarmos de init, garantimos que o recipiente buffer foi criado em um estado vazio.

cbuf_handle_t circular_buf_init(uint8_t* buffer, size_t size){assert(buffer && size);cbuf_handle_t cbuf = malloc(sizeof(circular_buf_t));assert(cbuf);cbuf->buffer = buffer;cbuf->max = size;circular_buf_reset(cbuf);assert(circular_buf_empty(cbuf));return cbuf;}

O propósito da função de reset é para colocar o tampão de um estado “vazio”, o que exige a atualização headtail e full:

void circular_buf_reset(cbuf_handle_t cbuf){ assert(cbuf); cbuf->head = 0; cbuf->tail = 0; cbuf->full = false;}

uma vez que temos um método para criar um buffer circular recipiente, precisamos de um método equivalente para destruir o recipiente. Neste caso, chamamos free em nosso contêiner. Não tentamos libertar o amortecedor subjacente, uma vez que não o possuímos.

void circular_buf_free(cbuf_handle_t cbuf){assert(cbuf);free(cbuf);}

verificações de Estado

a seguir, vamos implementar as funções relacionadas com o estado do recipiente tampão.

A função completa é a mais fácil de implementar, uma vez que temos uma bandeira que representa o estado:

bool circular_buf_full(cbuf_handle_t cbuf){assert(cbuf); return cbuf->full;}

como a full sinalizador para diferenciar entre cheios ou vazios estado, podemos combinar a bandeira de uma seleção que head == tail:

bool circular_buf_empty(cbuf_handle_t cbuf){assert(cbuf); return (!cbuf->full && (cbuf->head == cbuf->tail));}

A capacidade de nossa memória intermédia foi fornecido durante a inicialização, então, nós apenas devolver esse valor para o usuário:

size_t circular_buf_capacity(cbuf_handle_t cbuf){assert(cbuf);return cbuf->max;}

para Calcular o número de elementos no buffer foi um problema mais complicado do que eu esperava. Muitos cálculos de tamanho propostos usam modulo, mas encontrei casos estranhos quando testei isso. Optei por um cálculo simplificado utilizando declarações condicionais.se o buffer estiver cheio, sabemos que a nossa capacidade está no máximo. Se head for maior-do que-ou-igual-ao tail, nós simplesmente subtraímos os dois valores para obter o nosso tamanho. Se tail for maior que head, precisamos compensar a diferença com max para obter o tamanho correto.

size_t circular_buf_size(cbuf_handle_t cbuf){assert(cbuf);size_t size = cbuf->max;if(!cbuf->full){if(cbuf->head >= cbuf->tail){size = (cbuf->head - cbuf->tail);}else{size = (cbuf->max + cbuf->head - cbuf->tail);}}return size;}

Adicionando e removendo dados

com as funções de contabilidade fora do caminho, é hora de escavar na carne: adicionando e removendo dados da fila.

Adicionar e remover dados de um buffer circular requer manipulação de head e tail ponteiros. Ao adicionar dados ao buffer, inserimos o novo valor no atual head localização, então avançamoshead. Quando removemos os dados do buffer, recuperamos o valor do actual tail pointer e avançamos depois tail.

adicionar dados ao buffer requer um pouco mais de pensamento, no entanto. If the buffer is full, we need to advance our tail pointer as well ashead. Também precisamos verificar se a inserção de um valor desencadeia a condiçãofull.

vamos implementar duas versões da funçãoput, então vamos extrair a nossa lógica de avanço ponteiro para uma função auxiliar. Se o nosso buffer já estiver cheio, avançamos tail. Nós sempre avançamos head por um. Após o ponteiro ter sido avançado, nós povoamos ofull flag verificando sehead == tail.

Note the use of the modulo operator (%) below. O módulo fará com que os valoreshead e tail sejam reiniciados para 0 quando o tamanho máximo for atingido. Isto garante que head e tail são sempre índices válidos do buffer de dados subjacente.

static void advance_pointer(cbuf_handle_t cbuf){assert(cbuf);if(cbuf->full) {cbuf->tail = (cbuf->tail + 1) % cbuf->max;}cbuf->head = (cbuf->head + 1) % cbuf->max;cbuf->full = (cbuf->head == cbuf->tail);}

como Miro Samek observou com ajuda, esta é uma operação computacional cara. Em vez disso, podemos usar a lógica condicional para reduzir o número total de instruções. A abordagem recomendada de Miro é:

if (++(cbuf->head) == cbuf->max) { cbuf->head = 0;}

Agora, advance_pointer será assim:

static void advance_pointer(cbuf_handle_t cbuf){assert(cbuf);if(cbuf->full) {if (++(cbuf->tail) == cbuf->max) { cbuf->tail = 0;}}if (++(cbuf->head) == cbuf->max) { cbuf->head = 0;}cbuf->full = (cbuf->head == cbuf->tail);}

podemos fazer uma função auxiliar semelhante que é chamada ao remover um valor do buffer. Quando removemos um valor, a bandeirafull é definida para false, e o ponteiro da cauda é avançado.

static void retreat_pointer(cbuf_handle_t cbuf){assert(cbuf);cbuf->full = false;if (++(cbuf->tail) == cbuf->max) { cbuf->tail = 0;}}

we’ll create two versions of theput function. A primeira versão insere um valor no buffer e avança o ponteiro. Se o buffer estiver cheio, o valor mais antigo será substituído. Este é o caso de uso padrão para um buffer circular

void circular_buf_put(cbuf_handle_t cbuf, uint8_t data){assert(cbuf && cbuf->buffer); cbuf->buffer = data; advance_pointer(cbuf);}

a segunda versão doput devolve um erro se o buffer estiver cheio. Isto é fornecido para fins de demonstração, mas nós não usamos esta variante em nossos sistemas.

int circular_buf_put2(cbuf_handle_t cbuf, uint8_t data){ int r = -1; assert(cbuf && cbuf->buffer); if(!circular_buf_full(cbuf)) { cbuf->buffer = data; advance_pointer(cbuf); r = 0; } return r;}

Para remover os dados do buffer, podemos acessar o valor de tail e, em seguida, atualizar o tail ponteiro. Se o buffer estiver vazio, não devolvemos um valor nem modificamos o ponteiro. Em vez disso, devolvemos um erro ao utilizador.

int circular_buf_get(cbuf_handle_t cbuf, uint8_t * data){ assert(cbuf && data && cbuf->buffer); int r = -1; if(!circular_buf_empty(cbuf)) { *data = cbuf->buffer; retreat_pointer(cbuf); r = 0; } return r;}

que completa a implementação da nossa biblioteca de buffer circular.

de Uso

Quando utilizar a biblioteca, o cliente é responsável por criar subjacente buffer de dados para circular_buf_init e uma cbuf_handle_t é retornado:

uint8_t * buffer = malloc(EXAMPLE_BUFFER_SIZE * sizeof(uint8_t));cbuf_handle_t cbuf = circular_buf_init(buffer, EXAMPLE_BUFFER_SIZE);

Este identificador é utilizado para interagir com todas as demais funções da biblioteca:

bool full = circular_buf_full(cbuf);bool empty = circular_buf_empty(cbuf);printf("Current buffer size: %zu\n", circular_buf_size(cbuf);

não se esqueça de livre tanto subjacente buffer de dados e o recipiente quando você é feito:

free(buffer);circular_buf_free(cbuf);

A test program which uses the circular buffer library can be found in the embedded-resources repository.

Modificações para a Remoção completa bandeira

Se você queria abandonar o full bandeira, você deverá verificar que o head é uma posição atrás da cauda para determinar se o buffer está cheio:

bool circular_buf_full(circular_buf_t* cbuf){// We determine "full" case by head being one position behind the tail// Note that this means we are wasting one space in the buffer return ((cbuf->head + 1) % cbuf->size) == cbuf->tail;}

Agora, se quisermos evitar o módulo operação, podemos usar lógica condicional em vez:

bool circular_buf_full(circular_buf_t* cbuf){// We need to handle the wraparound case size_t head = cbuf->head + 1; if(head == cbuf->max) {head = 0; }return head == cbuf->tail;}

A caixa vazia é então que head e tail são os mesmos:

bool circular_buf_empty(circular_buf_t* cbuf){// We define empty as head == tail return (cbuf->head == cbuf->tail);}

Quando a obtenção de dados da memória intermédia, vamos avançar o ponteiro da cauda, envolvendo em torno, se necessário:

int circular_buf_get(circular_buf_t * cbuf, uint8_t * data){ int r = -1; if(cbuf && data && !circular_buf_empty(cbuf)) { *data = cbuf->buffer; cbuf->tail = (cbuf->tail + 1) % cbuf->size; r = 0; } return r;}

Quando a adição de dados para o buffer, vamos armazenar os dados e avançar o ponteiro de cabeça, enrolando, se necessário:

int circular_buf_put(circular_buf_t * cbuf, uint8_t data){ int r = -1; if(cbuf && !circular_nuf_full(cbuf)) { cbuf->buffer = data; cbuf->head = (cbuf->head + 1) % cbuf->size; r = 0; } return r;}

Outras referências full pode ser eliminado.

C++

C++ presta-se a uma implementação de tampão circular mais limpa do que C.

definição de classe

vamos começar por definir a nossa Classe C++. Queremos que a nossa implementação em c++ Suporte qualquer tipo de dados, por isso vamos torná-la uma classe templated.as nossas APIs vão ser semelhantes à implementação C. Nossa classe irá fornecer interfaces para:

  • Redefinir o buffer vazio
  • Adicionar dados
  • Remoção de dados
  • Verificação de cheio/vazio de estado
  • Verificar o número de elementos no buffer
  • Verificar a capacidade total da memória intermédia

Vamos também utilizar C++ ponteiros inteligentes para garantir a certeza de que não deixar quaisquer dados em torno de uma vez a nossa memória intermédia é destruída. Isto significa que podemos gerenciar o buffer para o usuário.

outro benefício do C++ é a trivialidade de tornar esta classe segura: podemos confiar no tipo std::mutex (assumindo que isto é definido para a sua plataforma).

aqui está a nossa definição de classe:

template <class T>class circular_buffer {public:explicit circular_buffer(size_t size) :buf_(std::unique_ptr<T>(new T)),max_size_(size){ // empty }void put(T item);T get();void reset();bool empty() const;bool full() const;size_t capacity() const;size_t size() const;private:std::mutex mutex_;std::unique_ptr<T> buf_;size_t head_ = 0;size_t tail_ = 0;const size_t max_size_;bool full_ = 0;};

C++ Implementation

o nosso buffer circular c++ imita grande parte da lógica da implementação C, mas resulta num desenho muito mais limpo e reutilizável. Além disso, o buffer c++ utiliza std::mutex para fornecer uma implementação segura de thread.

Nota: as mesmas mudanças lógicas podem ser feitas na implementação em C++ para suportar a segurança de thread com um único produtor e consumidor “desperdiçando” um slot. Veja os ajustes na implementação C para mais informações.

inicialização

ao construir a nossa classe, atribuímos os dados para o nosso buffer subjacente e ajustamos o tamanho do buffer. Isto remove as despesas gerais necessárias com a implementação em C.

Ao contrário da implementação em C, O Construtor de C++ não chama reset. Porque especificamos os valores iniciais para as variáveis de nossos membros, nosso buffer circular começa no estado correto.

explicit circular_buffer(size_t size) :buf_(std::unique_ptr<T>(new T)),max_size_(size){//empty constructor}

nosso comportamento de reset coloca o buffer de volta a um estado vazio (head == tail && !full_).

void reset(){std::lock_guard<std::mutex> lock(mutex_);head_ = tail_;full_ = false;}

controle de Estado

A lógica de empty e full casos é o mesmo que o C exemplo:

bool empty() const{//if head and tail are equal, we are emptyreturn (!full_ && (head_ == tail_));}bool full() const{//If tail is ahead the head by 1, we are fullreturn full_;}

Em C++ buffer circular de implementação, size e capacity relatório o número de elementos na fila, ao invés do que o tamanho em bytes. Isso nos permite ser agnósticos para os detalhes subjacentes do tipo.

size_t capacity() const{return max_size_;}size_t size() const{size_t size = max_size_;if(!full_){if(head_ >= tail_){size = head_ - tail_;}else{size = max_size_ + head_ - tail_;}}return size;}

Adding Data

The logic for put matches the C implementation. Esta implementação usa o padrão comportamental “overwrite the oldest value”.

void put(T item){std::lock_guard<std::mutex> lock(mutex_);buf_ = item;if(full_){tail_ = (tail_ + 1) % max_size_;}head_ = (head_ + 1) % max_size_;full_ = head_ == tail_;}

Nota: para simplificar, deixei de fora a opção que evita as operações de modo. Você pode encontrar essa lógica na Seção C.

Recuperação de Dados

A lógica por trás de get corresponde a C implementação. Ao contrário da implementação C, um valor vazio é devolvido se o buffer estiver vazio.

T get(){std::lock_guard<std::mutex> lock(mutex_);if(empty()){return T();}//Read data and advance the tail (we now have a free space)auto val = buf_;full_ = false;tail_ = (tail_ + 1) % max_size_;return val;}

Nota: return T() irá devolver um valor calculado por defeito fo Ra dado tipo. O valor real produzido depende do tipo ou do construtor. Além disso, por simplicidade, deixei de fora a opção que evita operações modulo. Você pode encontrar essa lógica na Seção C.

Utilização

o buffer circular C++ é muito mais simples de usar do que a implementação C.

para instanciar um buffer circular, basta declarar um objeto e especificar o tipo templated para o nosso buffer. Aqui está um exemplo usando um buffer de 10 uint32_t entradas:

circular_buffer<uint32_t> circle(10);

Adicionar dados é fácil:

uint32_t x = 100;circle.put(x);

E a obtenção de dados é igualmente fácil:

x = circle.get()

Lembre-se de que uma vez que este é um modelo de classe, você pode criar um buffer circular de qualquer tipo que você precisa.

Atualizações para C++17

Em C++17, temos acesso a std::optional, o que nos permite representar um valor que pode ou não estar presente. Nossa função getretornaria astd::optional<T>. We would also return std::nullopt instead of a default-constructed T if the queue is empty.

std::optional<T> get(){std::lock_guard<std::mutex> lock(mutex_);if(empty()){return std::nullopt;}//Read data and advance the tail (we now have a free space)auto val = buf_;full_ = false;tail_ = (tail_ + 1) % max_size_;return val;}

Nota: para simplificar, deixei de fora a opção que evita as operações de modo. Você pode encontrar essa lógica na Seção C.

no código de chamada, poderá verificar se existe um valor válido usando o operador booleano ou a função has_value membro. Se um valor válido estiver presente, ele pode ser acessado usando o-> ou* operadores, ur usando a função membro.

// Returns an optionalauto item = cbuf.get();// Check if the optional is validif(item){process_data(*item); // access the value}

colocando tudo junto

example implementations can be found in the embedded-resources GitHub repository.

  • C buffer circular programa de teste
    • C buffer circular biblioteca
  • C++ buffer circular exemplo

Se você está procurando ampliar esta biblioteca, um exercício útil é adicionar APIs adicionais para permitir que os usuários para adicionar/remover vários elementos com uma única operação. Você também pode tornar o C implementação thread-seguro.

segurança de rosca com o método Lookahead

uma abordagem para segurança de rosca sem um mutex é o método lookahead. Este método suporta um único fio de produção e um único fio de consumo; múltiplos produtores ou consumidores necessitarão de uma fechadura.

em vez de usar a bandeira booleana para diferenciar entre os casos completos e vazios, vamos sempre deixar uma célula vazia. Usando uma única célula vazia para detectar o caso “completo”, podemos suportar um único produtor e consumidor sem um bloqueio (desde que put e get não modifique as mesmas variáveis).

Você pode estar preocupado em desperdiçar um slot, mas este tradeoff é muitas vezes muito mais barato do que o custo de usar um OS lock primitivo.

Ler Mais

  • C++ Ponteiros Inteligentes
  • Vala Seu C-Chiqueiro Ponteiros para Ponteiros Inteligentes

Aqui estão outras buffer circular implementações:

  • QuantumLeaps/lock-livre-anel-buffer
  • willemt/BipBuffer

Para mais informações sobre buffers circulares:

  • Embedded.com: buffer de Anel básico
  • Taxas: buffer Circular
  • Ferrosos Sistemas: Lock Free Buffer de Anel
  • Boost Buffer Circular
  • C++: Performance of a Circular Buffer vs Vector, Deque, and List
  • Lock-Free Single-Producer-Single Consumer circular Queue

There is a proposal for adding a circular buffer type to the C++ standard library:

  • P0059: Uma Proposta para Adicionar Anel de Span para a Biblioteca Padrão
    • Anel Span
    • Anel Span Lite

Log de Alteração

  • 20210321
    • Corrigido o erro com max_size_ onde cbuf->max deve ter sido usado
    • Adicionado esclarecer texto sobre por que o buffer é passado pelo usuário
  • 20210301
    • Abordados feedback de Miro relação evitar o módulo de operações
  • 20210213
    • adicional links
    • Adicionado mais discussão sobre o tradeoff entre o total sinalizador de vs usando um “desperdício” slot
    • Mostrar modificações para a segurança do thread com um único produtor/consumidor
    • Adicionar notas sobre o std::optional use com C++17
  • 20191016
    • Atualização de Registo de Alteração de formatação da secção de consistência através do site
    • Rebaixado cabeçalhos para manter a consistência entre o site
    • Fixo quebrado Tabela de Conteúdos links
    • Removidas Log de Alteração da Tabela de Conteúdo
  • 20190627
    • Adicionado o link para metais Ferrosos Sistemas de’ Lock Free Buffer de Anel artigo.
  • 20190604
    • Fixed a typo (thanks Chris Svec!) e alterou algumas palavras relacionadas com o tipo opaco.
  • 20181219
    • adicionou uma nota sobre evitar problemas de concorrência com um único produtor e consumidor usando um espaço vazio.o artigo foi reestruturado e reescrito.Obrigado a todos que forneceram feedback ao longo do caminho. Os exemplos foram actualizados para:
    • Remover defensiva de programação
    • Use afirmações
    • Criar uma biblioteca autônoma usando uma estrutura opaca
    • Expandir as APIs, incluindo um cálculo para a corrente circular de tamanho de buffer
    • Atualizar biblioteca, de modo a não perder um slot

Deixe uma resposta

O seu endereço de email não será publicado.