提出的前提:
在程序设计中,我们有时会遇到这样的情况,一个线程将数据写到一个buffer中,另一个线程从中读数据,所以就有多线程竞争的问题。
通常的解决办法是对竞争资源加锁,但是,一般加锁的损耗比较高,其实对于这样的一个线程写,一个线程读的特殊情况,可以以一种简单的无锁ringbuffer来实现,这样的代码的运行效率会提高很多。
设计原理:
如图所示,假定buffer的长度是bufferSize. 我们设置两个指针。head指向的是下一次读的位置,而tail指向的是下一次写的位置。由于这里是环形buffer (ring buffer),这里就有一个问题,怎样判断buffer是满或者空。
这里采用的规则是,buffer的最后一个单元不存储数据。所以,如果head == tail,那么说明buffer为空。如果 head == tail + 1 (mod bufferSize),那么说明buffer满了。
接下来就是最重要的内容了:怎样以无锁的方式进行线程安全的buffer的读写操作。基本原理是这样的。在进行读操作的时候,我们只修改head的值,而在写操作的时候我们只修改tail的值。在写操作时,我们在写入内容到buffer之后才修改tail的值;而在进行读操作的时候,我们会读取tail的值并将其赋值给copyTail。
赋值操作是原子操作。所以在读到copyTail之后,从head到copyTail之间一定是有数据可以读的,不会出现数据没有写入就进行读操作的情况。同样的,读操作完成之后,才会修改head的数值;而在写操作之前会读取head的值判断是否有空间可以用来写数据。
所以,这时候tail到head - 1之间一定是有空间可以写数据的,而不会出现一个位置的数据还没有读出就被写操作覆盖的情况。这样就保证了RingBuffer的线程安全性。
C代码实现:
typedef void (*ringbuffer_data_destroy_t)(void* ctx,void* data);
typedef struct _ringbuffer_
{
void *buffer; //这里的void*型改成自己的数据类型
voliate int head;
voliate int tail;
int buffersize;
int typesize;
ringbuffer_data_destroy_t data_destroy;
}ringbuffer;
ringbuffer* ringbuffer_create(int typesize,int bufsize,ringbuffer_data_destroy_t data_destroy)
{
ringbuffer* thiz = (ringbuffer*)calloc(1,siezof(ringbuffer));
if(NULL != thiz)
{
thiz->head =0;
thiz->tail =0;
thiz->buffersize =bufsize;
thiz->typesize =typesize;
thiz->buffer= calloc(1,sizeof(typesize)*bufsize);
thiz->data_destroy = data_destroy;
}
return thiz;
}
bool_t ringbuffer_isempty(ringbuffer* ringbuffer)
{
return( ringbuffer->head == ringbuffer->tail ? true: false);
}
boot_t ringbuffer_isfull(ringbuffer* ringbuffer)
{
return ((ringbuffer->tail +1) % ringbuffer->buffersize == ringbuffer->head ? true:false);
}
int ringbuffer_put(ringbuffer* ringbuffer,void* data)
{
if(ringbuffer_isfull(ringbuffer))
{
return false;
}
memcpy(ringbuffer->buffer[ringbuffer->tail],data, ringbuffer->typesize);
ringbuffer->tail = (ringbuffer->tail + 1) % ringbuffer->buffersize;
return true;
}
void* ringbuffer_get(ringbuffer* ringbuffer)
{
if (ringbuffer_isempty(ringbuffer)) {
return null;
}
void * data = malloc(ringbuffer->typesize);
memcpy(data,ringbuffer->buffer[head],ringbuffer->typesize);
ringbuffer->head = (ringbuffer->head + 1) % ringbuffer->buffersize;
return data;
}
void* ringbuffer_getall(ringbuffer* ringbuffer,int *size)
{
if (ringbuffer_isempty()) {
return null;
}
int copytail = ringbuffer->tail;
int cnt = ringbuffer->head < copytail ? copytail - ringbuffer->head : ringbuffer->buffersize - ringbuffer->head + copytail;
void* data = malloc(cnt *ringbuffer->typesize);
if(ringbuffer->head < copytail)
{
for (int i =ringbuffer->head; i < copyTail; i++) {
memcpy(data + i* ringbuffer->typesize,ringbuffer->buffer[i],ringbuffer->typesize) ;
}
}
else
{
int i = 0, j = 0;
for(i = ringbuffer->head; i < ringbuffer->buffersize; i++)
{
memcpy(data + i* ringbuffer->typesize,ringbuffer->buffer[i],ringbuffer->typesize) ;
}
for(j = 0; j < copyTail; j++)
{
memcpy(data + i* ringbuffer->typesize +j* ringbuffer->typesize,ringbuffer->buffer[j],ringbuffer->typesize);
}
}
ringbuffer->head = copytail;
*size = cnt;
return data;
}
void ringbuffer_destory(ringbuffer* thiz)
{
if(thiz != NULL)
{
if(thiz->buffer !=NULL)
{
int i = 0
for(i = 0; i < thiz->buffersize;i++)
{
thiz->data_destroy(thiz->buffer[i]);
}
}
free(thiz);
}
}