这是在c++中实现有界缓冲区的正确方法吗?

Is this a correct way to implement a bounded buffer in C++

本文关键字:方法 缓冲区 c++ 实现      更新时间:2023-10-16

我正在编写一个程序,该程序处理多个线程访问、存入和从有界缓冲区容器中取出的情况。我注意到线程的一些主要问题,并怀疑我的缓冲区在某个地方部分或根本不正确。

为了确保我知道我在做什么,我希望有我的缓冲区代码检查。该类使用我在其他地方实现的信号量,我假设它现在可以工作(如果不行,我很快就会弄清楚!)我补充了一些评论,试图解释我的推理。

首先,.h文件:
#ifndef BOUNDED_BUFFER_H
#define BOUNDED_BUFFER_H
#include "Semaphore.H"
#include <string> 
#include <vector>  
using namespace std; 
class Item{ //supposed to eventually be more extensible...seems silly with just a string for now
  public:
    Item(string _content){content = _content;} 
    string GetContent() {return content;}     
  private:  
};   
    class BoundedBuffer{
      public:
        BoundedBuffer(); 
        void Deposit(Item* _item); 
        Item* Retrieve();        
        int GetNumItems() {return count;} 
        vector<Item*> GetBuffer() {return buffer;} 
        void SetSize(int _size){
          capacity = _size;
          buffer.reserve(_size);  //or do I need to call "resize" 
        }  
      private:
        int capacity; 
        vector<Item*> buffer; //I originally wanted to use an array but had trouble with  
                              //initilization/sizing, etc. 
        int nextin; 
            int nextout; 
            int count; 
            Semaphore notfull;   //wait on this one before depositing an item
            Semaphore notempty;  //wait on this one before retrieving an item
        };
    #endif

接下来,.cpp:

#include "BoundedBuffer.H"
#include <iostream>
using namespace std; 
BoundedBuffer::BoundedBuffer(){
  notfull.SetValue(0); 
  notempty.SetValue(0); 
  nextin = 0; 
  nextout = 0; 
  count = 0; 
}
void BoundedBuffer::Deposit(Item* _item){
  if(count == capacity){ 
    notfull.P(); //Cannot deposit into full buffer; wait
  }
  buffer[nextin] = _item; 
  nextin = (nextin + 1) % capacity;  //wrap-around
  count += 1;
  notempty.V();  //Signal that retrieval is safe 
}
Item* BoundedBuffer::Retrieve(){
  if(count == 0){
    notempty.P(); //cannot take from empty buffer; wait 
  }
  Item* x = buffer[nextout]; 
  nextout = (nextout + 1) % capacity;
  buffer.pop_back();  //or a different erase methodology? 
  count -= 1; 
  notfull.V(); //Signal that deposit is safe 
  return x; 
}

我认为问题可能是由于我选择向量作为底层容器(或者更确切地说,是对它的不正确使用),或者可能是需要更多的安全阻塞机制(互斥锁等?)从情况来看,有人能提供一些反馈吗?

这是一个非常常见的问题(关于如何做一个合适的多线程队列)。我以前见过的最好的答案是这个堆栈溢出问题和这个网站。这些答案适用于无界队列,因此我将在这里展开并展示一个适用于有界队列的答案。

您需要用互斥锁保护您的存款和检索函数,并使用条件变量来进行唤醒。

#include <mutex>
#include <condition_variable>
std::mutex the_mutex;
std::condition_variable the_notfull_cvar;
std::condition_variable the_notempty_cvar;
...
void BoundedBuffer::Deposit(Item* _item){
  std::unique_lock<std::mutex> lock(the_mutex);
  while ( /* buffer is full */ ){
    /* simultaneously wait and release the mutex */
    the_notfull_cvar.wait(lock);
    /* the mutex is reaquired at this point */
  }
  /* buffer has space and we own the mutex: insert the item */
  ...
  /* tell anyone waiting on an empty buffer that they can wake up. */
  the_notempty_cvar.notify_all();
}
Item* BoundedBuffer::Retrieve(){
  std::unique_lock<std::mutex> lock(the_mutex);
  while ( /* buffer is empty */ ){
    /* simultaneously wait and release the mutex */
    the_notempty_cvar.wait(lock);
    /* the mutex is reaquired at this point */
  }
  /* buffer has something in it and we own the mutex: get the item */
  ...
  /* tell anyone waiting on a full buffer that they can wake up. */
  the_notfull_cvar.notify_all();
  return x;
}

你的GetNumItems(), GetBuffer()和SetSize()函数也需要用unique_locks保护。

你看过Boost:Circular_buffer吗?这是一个固定大小的存储场所,具有标准库接口。这可能是你想要的,或者给你一些提示。如果在循环缓冲区已满时写入内容,则循环缓冲区将覆盖开始部分。也许你不希望这样,尽管有一个完整的()测试,所以你可以避免在你的代码。