C/C++ 线程安全的无锁ringbuffer的实现

提出的前提:

在程序设计中,我们有时会遇到这样的情况,一个线程将数据写到一个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);
    }
}


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
Linux内核中的无锁(lock-free)技术主要用于实现高效的并发数据结构,以提高系统的性能和吞吐量。其中,无锁环形缓冲区(lock-free ring buffer)是一种常用的数据结构,它可以高效地实现在多个线程之间传递数据的功能。 无锁环形缓冲区的实现原理如下: 1. 环形缓冲区的数据结构:无锁环形缓冲区由一个固定大小的环形数组和两个指针构成,一个是读指针,一个是写指针。读指针指向下一个将要读取的元素,写指针指向下一个将要写入的元素。 2. 原子操作:无锁环形缓冲区的实现依赖于原子操作(atomic operations),这些操作是在单个CPU指令中执行的,不会被其他线程中断。在Linux内核中,原子操作是通过宏定义实现的,如“atomic_add()”、“atomic_sub()”等。 3. 写入数据:当一个线程想要写入数据时,它首先需要检查缓冲区是否已满。如果缓冲区已满,则写入操作失败。如果缓冲区未满,则该线程会使用原子操作将数据写入缓冲区,并更新写指针。 4. 读取数据:当一个线程想要读取数据时,它首先需要检查缓冲区是否为空。如果缓冲区为空,则读取操作失败。如果缓冲区不为空,则该线程会使用原子操作将数据从缓冲区中读取,并更新读指针。 5. 线程同步:无锁环形缓冲区的实现不依赖于任何锁机制,因此可以避免锁竞争和死锁等问题。不过,在多个线程并发读写的情况下,需要使用一些同步机制来保证线程安全,如使用原子操作或者memory barrier等技术。 总的来说,无锁环形缓冲区是一种高效的并发数据结构,能够在多个线程之间高效地传递数据,提高系统的性能和吞吐量。在Linux内核中,无锁环形缓冲区的实现依赖于原子操作和线程同步技术,可以避免锁竞争和死锁等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水火汪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值