CおよびC++での循環バッファの作成

Updated:20210321

組み込みシステムのリソース制約の性質のため、循環バッ

循環バッファ(リングバッファとも呼ばれます)は、メモリが連続しているかのように動作する固定サイズのバッファです&循環 メモリが生成されて消費されると、データを再シャッフルする必要はなく、むしろhead/tailポインタが調整されます。 データが追加されると、ヘッドポインタが進みます。 データが消費されると、末尾ポインタが進みます。 バッファの最後に到達すると、ポインタは単に先頭に折り返されます。

循環バッファ操作のより詳細な概要については、Wikipediaの記事を参照してください。 この記事の残りの部分では、循環バッファがどのように機能するかを理解していることを前提としています。

目次:

  1. なぜ循環バッファを使用するのですか?
  2. C実装
    1. カプセル化を使用して
    2. APIデザイン
    3. バッファがいっぱいかどうかを判断する
    4. 循環バッファコンテナタイプ
    5. 実装
    6. 使用法
    7. fullフラグ
  3. C++実装
    1. クラス定義
    2. 実装
    3. 使用法
    4. c++17の更新
  4. それをすべて一緒に置く
  5. さらに読む

なぜ循環バッファを使用するのですか?

循環バッファは、多くの場合、固定サイズのキューとして使用されます。 固定サイズは、開発者が動的な割り当てではなく静的なデータ記憶方法を使用しようとすることが多いため、組み込みシステムにとって有益です。

循環バッファは、データの生成と消費が異なる速度で発生する状況にも便利な構造です。 消費者が生産に追いつくことができない場合、古いデータはより新しいデータで上書きされます。 循環バッファを使用することで、常に最新のデータを消費していることを確認できます。

追加のユースケースについては、Ring Buffer Basics onをチェックしてくださいEmbedded.com…..

Cの実装

循環バッファライブラリを作成するときに設計上の課題とトレードオフのいくつかにさらされるため、Cの実装から始めます。

Encapsulationの使用

循環バッファライブラリを作成しているので、構造を直接変更するのではなく、ユーザーがライブラリApiを使用するようにしたいと思 また、エンドユーザーがコードを更新する必要なく、必要に応じて変更できるように、ライブラリ内に実装を保持したいと考えています。 ユーザーは、構造の詳細を知る必要はなく、存在することだけを知る必要があります。

ライブラリヘッダーの中で、構造体を前方宣言します。

// Opaque circular buffer structuretypedef struct circular_buf_t circular_buf_t;

ユーザーがcircular_but_tポインタを直接操作したくないので、値を逆参照できるという印象を受ける可能性があります。 代わりに使用できるハンドル型を作成します。

ハンドルの最も簡単なアプローチは、typedefcbuf_handle_tuintptr_tvoid*値にすることです。 インターフェイスの内部では、適切なポインタ型への変換を処理します。 私たちは循環バッファタイプをユーザーから隠されたままにしておき、データと対話する唯一の方法はハンドルを介して行うことです。

サンプルコードをシンプルで簡単に保つために、単純なハンドルの実装に固執します。

API Design

まず、ユーザーが循環バッファとどのように対話するかを考える必要があります:

  • 彼らは、バッファとサイズを持つ循環バッファコンテナを初期化する必要があります
  • 彼らは、循環バッファコンテナを破壊する必要があります
  • 彼らは、循環バッファコンテナをリセットする必要があります
  • 彼らは、バッファにデータを追加することができるようにする必要があります
  • 彼らは、バッファから次の値を取得できるようにする必要があります
  • 彼らは、バッファがいっぱいか空であるかを知る必要があります
  • 彼らは、バッファ内の要素の現在の数を知る必要がありますバッファ
  • 彼らはバッファの最大容量を知る必要があります

このリストを使用して、我々は一緒に私たちのライブラリのapiを置くことがで ユーザーは、初期化中に作成される不透明なハンドルタイプを使用して、循環バッファライブラリと対話します。この実装では、基礎となるデータ型としてuint8_tを選択しました。 基本となるバッファとバイト数を適切に処理するように注意してください。

/// 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);

バッファがいっぱいかどうかの判断

先に進む前に、バッファがいっぱいか空かどうかを判断するために使用する方法を議論す

循環バッファの”完全”と”空”の両方のケースは同じように見えます。headtailポインタは等しいです。 フルと空を区別するには二つのアプローチがあります。

  1. バッファ内のスロットを”廃棄”します。
    • フル状態はhead + 1 == tail
    • 空の状態はhead == tail
  2. 使用abool
  3. 使用Abool
  4. 使用A
  5. 使用Abool
  6. 使用A
  7. 使用A
  8. 使用A
  9. 使用A
  10. 使用Aフラグと状態を区別するための追加ロジック::
    • 完全な状態はfull
    • 空の状態は(head == tail) && !full

スレッドの安全性も考慮する必要があります。 単一の空のセルを使用して”完全な”ケースを検出することにより、ロックなしで単一のプロデューサーと単一のコンシューマーをサポートできます(putgetheadtailfullfullフラグがプロデューサとコンシューマの両方で共有されているためです。もちろん、決定にはトレードオフがあります。

もちろん、決定にはトレードオフがあります。

バッファ要素に大きなメモリフットプリントがある場合(カメラiフレーム用のサイズのバッファなど)、スロットを無駄にすることは、システム上で合理的ではない可能性があります。 複数の生産者/消費者がキューと対話している場合は、とにかくロックが必要になるため、スロットを無駄にすることは意味がありません。 相互排他を利用できない場合(たとえば、OSを使用していないため)、割り込みを使用している場合は、非fullboolgetputfullフラグを使用しない単一のプロデューサ/コンシューマに対して変更を行う方法についても説明します。

Circular Buffer Container Type

サポートする必要がある操作を把握したので、circular buffer containerを設計できます。

バッファの状態を管理するためにコンテナ構造を使用します。 カプセル化を維持するために、コンテナ構造は、ヘッダーではなく、ライブラリ.cファイル内で定義されています。

私たちは、追跡する必要があります:

  • 基になるデータバッファ
  • バッファの最大サイズ
  • 現在の”頭”の位置(要素が追加されたときにインクリメンコンテナが設計されたので、ライブラリ関数を実装する準備が整いました。

    実装

    注意すべき重要な詳細は、各Apiに初期化されたバッファハンドルが必要であることです。 コードを条件文で捨てるのではなく、アサーションを利用してAPI要件を”Design by Contract”スタイルで強制します。

    インターフェイスが不適切に使用されている場合、プログラムはユーザーにエラーコードのチェックと処理を要求するのではなく、すぐに失敗します。たとえば、次のようにします。

    circular_buf_reset(NULL);

    は次のように生成します。

    :

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

    もう一つの重要な注意は、以下に示す実装がスレッドセーフではないということです。 基になる循環バッファライブラリにロックは追加されていません。

    初期化とリセット

    最初から始めましょう:循環バッファの初期化。 私たちのAPIは、クライアントが基になるバッファとバッファサイズを提供し、循環バッファハンドルを返します。 ユーザーにバッファを提供してほしいのは、これが静的に割り当てられたバッファを可能にするからです。 APIが内部でバッファを作成した場合、組み込みシステムプログラムではしばしば許可されていない動的メモリ割り当てを利用する必要があります。

    私たちは、ユーザーへのポインタを返すことができるように、ライブラリ内の循環バッファ構造インスタンスを提供する必要があります。 簡単にするためにmallocinit関数を変更して、事前に割り当てられた循環バッファ構造の静的プールからの割り当てなど、別の方法を使用する必要があります。

    別のアプローチは、カプセル化を中断し、ユーザーがライブラリの外部で循環バッファコンテナ構造を静的に宣言できるようにすることです。 この場合、circular_buf_initinit関数でスタック上にコンテナ構造を作成し、それを卸売して返すこともできます。 しかし、カプセル化が壊れているため、ユーザーはライブラリルーチンを使用せずに構造を変更することができます。 カプセル化を維持したい場合は、具象構造インスタンスの代わりにポインタを使用する必要があります。p>

    // 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);

    ライブラリ内に割り当てられている構造体へのハンドルを返します。 コンテナを作成したら、値を入力してresetinitheadtailfull:

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

    循環バッファコンテナを作成するメソッドがあるので、コンテナを破棄するための同等のメソッドが必要です。 この場合、コンテナ上でfreeを呼び出します。 私たちはそれを所有していないので、基礎となるバッファを解放しようとしません。

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

    状態チェック

    次に、バッファコンテナの状態に関連する関数を実装します。

    完全な関数は、状態を表すフラグを持っているので、実装するのが最も簡単です:

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

    完全または空の状態を区別するためのfullhead == tail:

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

    バッファの容量したがって、その値をユーザーに返すだけです。

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

    バッファ内の要素数の計算は、私が予想していたよりもトリッキーな問題でした。 多くの提案されたサイズ計算はmoduloを使用していますが、それをテストするときに奇妙なコーナーケースに遭遇しました。 私は条件文を使用して単純化された計算を選択しました。

    バッファがいっぱいの場合、容量は最大であることがわかります。 もしheadtailtailheadmaxとの差を相殺する必要があります。

    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;}

    データの追加と削除

    簿記関数を使って、キューからデータを追加したり削除したりすることができます。

    循環バッファからデータを追加および削除するには、headtailheadheadtailtailを進めます。

    バッファにデータを追加するには、もう少し考える必要があります。 バッファがfulltailheadfull条件がトリガーされるかどうかを確認する必要があります。

    puttailheadhead == tailfull%)の使用に注意してください。 Moduloは、最大サイズに達すると、headtailheadtailが常に基になるデータバッファの有効なインデックスであることが保証されます。

    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);}

    Miro Samekが親切に指摘したように、これは高価な計算操作です。 代わりに、条件付きロジックを使用して、命令の総数を減らすことができます。 ミロの推奨されるアプローチは次のとおりです。

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

    さて、advance_pointer次のようになります:

    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);}

    バッファから値を削除するときに呼び出される同様のヘルパー関数を作ることができます。 値を削除すると、fullfalseに設定され、末尾ポインタが進められます。p>

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

    put関数の二つのバージョンを作成します。 最初のバージョンでは、バッファに値を挿入し、ポインタを進めます。 バッファがいっぱいの場合は、最も古い値が上書きされます。 これは、循環バッファの標準的な使用例です

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

    put関数の第二のバージョンは、バッファがいっぱいの場合、エラーを返します。 これはデモ目的で提供されていますが、このバリアントはシステムでは使用しません。

    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;}

    バッファからデータを削除するには、tailtailポ バッファが空の場合、値を返したり、ポインタを変更したりしません。 代わりに、ユーザーにエラーを返します。

    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;}

    私たちの循環バッファライブラリの実装を完了します。

    使用法

    ライブラリを使用する場合、クライアントは基礎となるデータバッファをcircular_buf_initcbuf_handle_tが返されます。

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

    このハンドルは、残りのすべてのライブラリ関数と対話するために使用されます。

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

    このハンドルは、

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

    このハンドルは、

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

    このハンドルは、残りのすべてのライブラリ関数と対話するために使用されます。

    完了したら、基礎となるデータバッファとコンテナの両方を解放することを忘れないでください:

    free(buffer);circular_buf_free(cbuf);

    循環バッファライブラリを使用するテストプログラムは、embedded-resourcesリポジトリにあります。

    完全なフラグを削除するための変更

    fullheadがバッファがいっぱいであるかどうかを判断するためにテールの後ろの一つの位置にあることを確認します。

    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;}

    今、私たちがしたい場合はモジュロ演算を避けるために、代わりに条件付き論理を使用できます:

    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;}

    空の場合は、headtail同じです。

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

    バッファからデータを取得するとき、必要に応じて/p>

    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;}

    バッファにデータを追加するときは、データを格納してヘッドポインタを進め、必要に応じてラップします。

    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;}

    fullへの他の参照を排除することができます。

    C++

    C++は、Cよりもクリーンな循環バッファ実装に適しています。

    クラス定義

    まず、C++クラスを定義します。 C++実装であらゆるタイプのデータをサポートしたいので、テンプレート化されたクラスにします。

    私たちのApiはCの実装に似ています。 私たちのクラスは、以下のインターフェイスを提供します:

    • バッファを空にリセットする
    • データを追加する
    • データを削除する
    • 完全/空の状態をチェックする
    • バッファ内の現在の要素数をチェックする
    • バッファの総容量をチェックする

    C++スマートポインタを利用して、バッファが破壊された後にデータを残さないようにします。 これは、ユーザーのバッファを管理できることを意味します。C++のもう一つの利点は、このクラスをスレッドセーフにすることの自明性です。std::mutex型に頼ることができます(これはプラットフォーム用に定義されていると仮定します)。

    ここに私たちのクラス定義があります:

    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++実装

    私たちのC++循環バッファは、C実装からのロジックの多くを模倣しますが、はるかにクリーンで再利用可能な設計になります。 また、c++バッファはstd::mutexを使用してスレッドセーフな実装を提供します。

    注:スロットを”無駄にする”ことによって、単一のプロデューサとコンシューマでスレッドセーフをサポートするために、C++実装で同じロジック変更を行 詳細については、C実装の調整を参照してください。

    初期化

    クラスを構築するとき、基礎となるバッファにデータを割り当て、バッファサイズを設定します。 これにより、C実装に必要なオーバーヘッドが削除されます。Cの実装とは異なり、C++コンストラクタはresetを呼び出しません。 メンバー変数に初期値を指定するため、循環バッファは正しい状態で開始されます。

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

    リセット動作により、バッファは空の状態に戻ります(head == tail && !full_)。

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

    状態追跡

    emptyfullケースのロジックは、Cの例と同じです。

    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_;}

    C++では、

    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_;}

    循環バッファの実装では、sizecapacityは、バイト単位のサイズではなく、キュー内の要素の数を報告します。 これにより、型の基礎となる詳細にとらわれないようにすることができます。P>

    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;}

    データの追加

    putのロジックは、Cの実装と一致します。 この実装では、”最も古い値を上書きする”動作パターンを使用します。注:簡単にするために、モジュロ演算を回避するオプションを省略しました。 あなたはCセクションでそのロジックを見つけることができます。P>

    データの取得

    getの背後にあるロジックは、Cの実装と一致します。 Cの実装とは異なり、バッファが空の場合は空の値が返されます。p>

    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;}

    注意: return T()は、指定されたタイプのデフォルト構築値を返します。 生成される実際の値は、型またはコンストラクターによって異なります。 また、簡単にするために、モジュロ演算を回避するオプションを省略しました。 あなたはCセクションでそのロジックを見つけることができます。

    使用法

    C++循環バッファは、C実装よりもはるかに簡単に使用できます。

    循環バッファをインスタンス化するには、オブジェクトを宣言し、バッファのテンプレート型を指定するだけです。 以下は、10個のバッファを使用した例ですuint32_tエントリ:

    circular_buffer<uint32_t> circle(10);

    データの追加は簡単です:

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

    データの取得も同様に簡単です:

    x = circle.get()

    これはテンプレート化されたクラスなので、必要な任意の型の循環バッファを作成できることに注意してください。

    C++17の更新

    C++17では、std::optionalgetstd::optional<T>Tstd::nulloptを返します。注:簡単にするために、モジュロ演算を回避するオプションを省略しました。 あなたはCセクションでそのロジックを見つけることができます。呼び出し元のコードでは、ブール演算子またはhas_value->*value()メンバー関数を使用してur。p>

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

    それをすべて一緒に置く

    実装の例は、embedded-resourcesGithubリポジトリにあります。

    • c circular buffer test program
      • C circular buffer library
    • C++circular buffer example

    このライブラリを拡張する場合は、ユーザーが単一の操作で複数の要素を追加/削除できるようにするApiを追加す また、C実装をスレッドセーフにすることもできます。

    先読みメソッドを使用したスレッドセーフ

    ミューテックスを使用しないスレッドセーフの一つのアプローチは、”先読み”メソッドです。 このメソッドは、単一のプロデューサースレッドと単一のコンシューマースレッドをサポートします; 複数の生産者や消費者は、ロックが必要になります。

    ブール値フラグを使用して完全なケースと空のケースを区別する代わりに、常に1つのセルを空のままにします。 単一の空のセルを使用して”完全な”ケースを検出することにより、ロックなしで単一のプロデューサーと単一のコンシューマーをサポートできます(putget同じ変数を変更しない限り)。

    スロットを無駄にすることを心配するかもしれませんが、このトレードオフは、多くの場合、OSロックプリミティブを使用するコストよりもはるかに安

    さらに読む

    • C++スマートポインタ
    • スマートポインタのためのC-Styeポインタを捨てる

    他の循環バッファ実装は次のとおりです。

    • QuantumLeaps/lock-free-ring-buffer
    • willemt/BipBuffer

    循環バッファの詳細については、

    • Embedded.com:リングバッファの基本
    • Wikipedia:循環バッファ
    • 鉄系システム:ロックフリーリングバッファ
    • 循環バッファをブースト
    • C++: 循環バッファとベクトル、Deque、およびリストのパフォーマンス
    • ロックフリーの単一生産者単一消費者循環キュー

    c++標準ライブラリに循環バッ: 標準ライブラリにリングスパンを追加する提案

    • リングスパン
    • リングスパンLite

変更ログ

  • 20210321
    • max_size_cbuf->maxcbuf->maxcbuf->maxcbuf->maxバッファがユーザーによって渡される理由に関する明確なテキストを追加しました
  • 20210301
    • モジュロ操作の回避に関するmiroからのフィードバッ slot
    • 単一のプロデューサー/コンシューマーでスレッドセーフの変更を表示
    • std::optionalC++17で使用
  • 20191016
    • サイト全体の一貫性のための変更ログセクショ>
    • 目次から変更ログを削除
  • 20190627
    • 鉄系システムのロックフリーリングバッファ記事へのリンクを追加しました。
  • 20190604
    • タイプミスを修正しました(Chris Svecに感謝します!)と不透明なタイプに関連するいくつかの文言を変更しました。
  • 20181219
    • 空のスロットを使用して、単一のプロデューサと単一のコンシューマとの同時実行の問題を回避することについての注意を追加しました。
  • 20180804
    • 記事が再構成され、書き直されました。道に沿ってフィードバックを提供したすべての人に感謝します。 例は次のように更新されています:
    • 防御プログラミングを削除する
    • アサーションを使用する
    • 不透明な構造を使用してスタンドアロンライブラリを作成する
    • 現在のサーキュラーバッファサイズの計算を含むApiを展開する
    • スロットを無駄にしないようにライブラリを更新する

コメントを残す

メールアドレスが公開されることはありません。