这几天在研究无锁编程的一些事情。

这里是内核kfifo(无锁循环队列,主要用于单一读者与单一写者)代码介绍:http://blog.csdn.net/linyt/article/details/5764312 代码精妙处原文作者已经解释得十分清楚了,然而,作者略过了这三个函数的介绍 :

smp_rmb()
smp_wmb()
smp_mb()

这几个函数用于同步CPU各个核的cache line。是不是太专业了一点,其实我也不太懂硬件方面的东西,但总归要知道cpu是多核的,每个核有自己的cache,读写内存都先通过cache。然后呢,内存只有一个,核有多个,也就是说,同一份数据在内存只有一份,但却可能同时存在于多个cache line中。

现在转过头来说另一个事实,所有无锁算法都依赖于一些原子操作,例如compare_and_set, atomic_add, atomic_sub之类的(这些操作和cpu本身提供的原子指令并不一样,原子指令例如cmpxchg只是确保在完成这个操作之前进程不会被抢占)。这些操作可以执行的一个前提是——独占。假如一个变量a同时存在于核1和核2的cache line中,那么当核1想要进行atomic_add的时候必须先独占这个变量a,也就是告诉核2变量a在你的cache line已经无效了,以后想要操作a的时候到我这里来取最新的值。是不是有点像锁呢?恩,是挺像的,这正是本文标题想要表达的意思。(如果变量a不在cache line中呢?那么核1要锁住的将不是cache line,而是内存总线,一个消耗更大的操作)

然后上面的三个函数,被称为内存屏障,用于cpu各个核的cache line通信,至于实际上通信的过程是怎样的,这篇写得不能再好(墙外):http://sstompkins.wordpress.com/2011/04/12/why-memory-barrier%EF%BC%9F/

同时,这三个函数只存在于内核源代码中,要在自己的代码中达到同样的效果,可以使用更上层的api,例如gcc 4.2以上的版本都内置提供__sync_synchronize()这类的函数,效果差不多。(或者你自己去抄内核的实现)

这里来盗窃一下内核的源代码,修改一下变成一个简单的“无锁”循环队列:

#define QUEUE_SIZE 1024
typedef unsigned int ElementType;

struct kfifo
{
    ElementType data[ QUEUE_SIZE ];
    unsigned int in; 
    unsigned int out;
};

int kfifo_put( struct kfifo *fifo, ElementType element )
{
    if ( QUEUE_SIZE - fifo->in + fifo->out == 0 )
        return 0;

    __sync_synchronize();      //确保取出的out是最新的(它是这么说的,但极度怀疑不需要)

    unsigned int index = fifo->in & (QUEUE_SIZE - 1);
    fifo->data[ index ] = element;

    __sync_synchronize();      //确保先写入数据再更新in

    fifo->in++;

    return 1;
}

int kfifo_get( struct kfifo *fifo, ElementType *element )
{
    if ( fifo->in - fifo->out == 0 )
        return 0;
    unsigned int index = fifo->out & (QUEUE_SIZE - 1);

    __sync_synchronize();       //确保读出的in是最新的(同上)

    *element = fifo->data[ index ];

    __sync_synchronize();       //确保先读取数据再更新out

    fifo->out++;

    return 1;
}

嘛,差不多了。

 posted on 2013-03-06 01:19  万事屋madao  阅读(4814)  评论(5编辑  收藏  举报