我正在尝试找到一种方法来实现无锁或非阻塞方式,为单个使用者/单个使用者创建一个环形缓冲区,它将覆盖缓冲区中最旧的数据。我已经阅读了很多无锁算法,如果缓冲区已满,则“返回false”时可以正常工作 - 即不添加;但是,当你需要覆盖最旧的数据时,我甚至找不到谈论如何做的伪代码。
我正在使用GCC 4.1.2(工作中的限制,我无法升级版本......)而且我有Boost库,而且在过去我制作了自己的Atomic< T>变量类型与upcomming规范非常接近(它不完美,但它是线程安全的并且做我需要的)。
当我想到它时,我认为使用这些原子应该真正解决问题。关于我在想什么的一些粗略的伪代码:
template< typename T , unsigned int Size>
class RingBuffer {
private:
Atomic<unsigned int> readIndex;
Atomic<unsigned int> writeIndex;
enum Capacity { size = Size };
T* buf;
unsigned int getNextIndex(unsigned int i)
{
return (i + 1 ) % size;
}
public:
RingBuffer() { //create array of size, set readIndex = writeIndex = 0 }
~RingBuffer() { //delete data }
void produce(const T& t)
{
if(writeIndex == getNextIndex(readIndex)) //1
{
readIndex = getNextIndex(readIndex); //2
}
buf[writeIndex] = t;
writeIndex = getNextIndex(writeIndex); //3
}
bool consume(T& t)
{
if(readIndex == writeIndex) //4
return false;
t = buf[readIndex];
readIndex = getNexIndex(readIndex); //5
return true;
}
};
据我所知,这里没有死锁情况,所以我们对此是安全的(如果上面的实现错误,即使在伪代码级别上也是如此,建设性的批评总是受到赞赏)。 但是,我能找到的大竞争条件是:
让我们假设缓冲区已满。也就是说,writeIndex +1 = readIndex; (1)发生,正如消耗被调用。是真的 (4)是假的,所以我们从缓冲区移动读取 (5)发生,并且readIndex是高级的(因此事实上,缓冲区中存在空间) (2)发生,推进readIndex AGAIN,从而丢失该值。
基本上,它的一个典型的写作问题必须修改读者,导致竞争条件。每次访问时都没有实际阻止整个列表,我想不出有办法防止这种情况发生。我错过了什么?
答案 0 :(得分:7)
答案 1 :(得分:1)
我错过了什么?
的数量:
do {
将值复制出来;使用修改序列num等} while (
corrupt )
但是,让我们把它写成低于你的伪代码级别,并考虑你明确的问题:
consume()
之前正确地对readIndex进行了采样/复制 - 在复制(可能已损坏)t
之前 - 如果生产者已经增加了CAS指令,则CAS指令将失败。而不是通常的重新采样和重试CAS,只需继续。答案 2 :(得分:0)
这是我最近创建的原子变量的循环缓冲区代码。我已将其修改为&#34;覆盖&#34;数据而不是返回false。 免责声明 - 尚未进行生产等级测试。
template<int capacity, int gap, typename T> class nonblockigcircular {
/*
* capacity - size of cicular buffer
* gap - minimum safety distance between head and tail to start insertion operation
* generally gap should exceed number of threads attempting insertion concurrently
* capacity should be gap plus desired buffer size
* T - is a data type for buffer to keep
*/
volatile T buf[capacity]; // buffer
std::atomic<int> t, h, ph, wh;
/* t to h data available for reading
* h to ph - zone where data is likely written but h is not updated yet
* to make sure data is written check if ph==wh
* ph to wh - zone where data changes in progress
*/
bool pop(T &pwk) {
int td, tnd;
do {
int hd=h.load()%capacity;
td=t.load()%capacity;
if(hd==td) return false;
tnd=(td+1)%capacity;
} while(!t.compare_exchange_weak(td, tnd));
pwk=buf[td];
return true;
}
const int count() {
return ( h.load()+capacity-t.load() ) % capacity;
}
bool push(const T &pwk) {
const int tt=t.load();
int hd=h.load();
if( capacity - (hd+capacity-tt) % capacity < gap) {
// Buffer is too full to insert
// return false;
// or delete last record as below
int nt=t.fetch_add(1);
if(nt==capacity-1) t.fetch_sub(capacity);
}
int nwh=wh.fetch_add(1);
if(nwh==capacity-1) wh.fetch_sub(capacity);
buf[nwh%capacity]=pwk;
int nph=ph.fetch_add(1);
if(nph==capacity-1) ph.fetch_sub(capacity);
if(nwh==nph) {
int ohd=hd;
while(! h.compare_exchange_weak(hd, nwh) ) {
hd=h.load();
if( (ohd+capacity-hd) % capacity > (ohd+capacity-nwh) % capacity ) break;
}
}
return true;
}
};