鸿蒙源码分析(五十八)

分布式调度之lock_free_qunne.c介绍

这一片主要是介绍lock_free_quene.c中的代码知识。
lock_free是一种线程中的无锁安全算法。无锁编程是指在不使用锁的情况下,在多线程环境下实现多变量的同步。即在没有线程阻塞的情况下实现同步。这样可以避免竞态、死锁等问题。
Lock-free 算法的基础是 CAS (Compareand-Swap) 原子操作。当某个地址的原始值等于某个比较值时,把值改成新值,无论有否修改,返回这个地址的原始值。目前的cpu 支持最多64位的CAS。并且指针 p 必须对齐。
注:原子操作指一个cpu时钟周期内就可以完成的操作,不会被其他线程干扰。
一般的 CAS 使用方式是:

  • 假设有指针 p, 它指向一个 32 位或者64位数,
  • 复制p 的内容(*p)到比较量 cmp (原子操作)
  • 基于这个比较量计算一个新值 xchg (非原子操作)
  • 调用 CAS 比较当前 *p 和 cmp, 如果相等把 *p 替换成 xchg (原子操作)
  • 如果成功退出,否则回到第一步重新进行

第3步的 CAS 操作保证了写入的同时 p 没有被其他线程更改。如果*p已经被其他线程更改,那么第2步计算新值所使用的值(cmp)已经过期了,因此这个整个过程失败,重新来过。多线程环境下,由于 3 是一个原子操作,那么起码有一个线程(最快执行到3)的CAS 操作可以成功,这样整体上看,就保证了所有的线程上在“前进”,而不需要使用效率低下的锁来协调线程,更不会导致死锁之类的麻烦。

代码解释

这里是最基础的创建功能,函数根据参数创建一个队列。
参数解释:

  1. size:参数确定一个队列的大小
  2. count:要申请队列的个数

函数中功能很明确,就是创建队列,这里使用到一个total变量,主要是用来计算总的size,这里计算方式sizecount+1,这里申请到空间后把该结构体的read和write置为0,itemsize表示的是单个队列的尺寸大小,这里的totalsize表示的是一共申请了count个队列后总的size大小。
这里我也不太明白为啥计算时候为啥要加一,个人理解就是size
count。。。。。。

//lock-free队列的创建
LockFreeQueue *LFQUE_Create(int size, int count)
{
    
    
    if (size <= 0 || count <= 0) {
    
    
        return NULL;
    }
    //判断参数是否合法
    int total = size * count + 1;
    //total计算为队列个数乘以队列size,然后+1,这里不太清楚加一的意义
    if (total <= 0) {
    
    
        return NULL;
    }   
    //
    register LockFreeQueue *queue = (LockFreeQueue *)SAMGR_Malloc(sizeof(LockFreeQueue) + total);
    if (queue == NULL) {
    
    
        return NULL;
    }
    queue->write = 0;
    queue->read = 0;
    queue->itemSize = size;
    queue->totalSize = total;
    return queue;
}

LFQUE_IsEmpty函数

这个函数主要用来判空lock_free的队列。这是一个bool函数,能够根据传入的队列来判断队列中是否为空。
判空原理就是队列中写的可写元素和可读元素是否相同,因为队列中write位置和read位置之后最开始是相等的,也就是没有元素空状态的时候,一旦开始读写这两个变量不会有

BOOL LFQUE_IsEmpty(LockFreeQueue *queue)
{
    
    
    return (queue->write == queue->read);
}

LFQUE_IsFull

队列的判满函数,来判断队列中是否元素饱和,达到满状态。这里的totalsize比实际size大1,可能就是为了这里的判满,指针后移一位如果刚好size等于totalsize,说明队列元素已经满了。

BOOL LFQUE_IsFull(LockFreeQueue *queue)
{
    
    
    uint32 nextWrite = queue->write + 1;
    //这里相当于一个指针	
    if (nextWrite >= queue->totalSize) {
    
    
        nextWrite = 0;
    }
    return (nextWrite == queue->read);
}

出队和入队函数push&pop

1.push函数
函数就是将一个指定元素压入队列。
参数:

  1. quene:要压入元素的队列
  2. element:要压队的元素
  3. pri:这个变量在函数中基本没有调用,这里我也不太清楚目的

这里就是队队列剩余空间进行计算,看看能不能入队,如果空间满了就说明线程忙,无法入队。如果剩余空间足够一个itemsize,就将元素在一个itemsize中全部写入。如果不足一个itemsize就将剩余空间写入

int LFQUE_Push(LockFreeQueue *queue, const void *element, uint8 pri)
{
    
    
    (void)pri;
    if (queue == NULL || element == NULL) {
    
    
        return EC_INVALID;
    }
    //先检查参数
    if (LFQUE_IsFull(queue)) {
    
    
        return EC_BUSBUSY;
    }
    //对队列进行判满,如果队列元素满了就无法入队了
    uint32 copyLen = (queue->totalSize - queue->write < queue->itemSize) ?
                  (queue->totalSize - queue->write) : queue->itemSize;
    //每次写入判断剩余空间是否可用
    if (memcpy_s(&queue->buffer[queue->write], copyLen, element, copyLen) != EOK) {
    
    
        return EC_INVALID;
    }
    //对队列缓冲区剩余位置进行初始化,内容为传入的element
    //等效于入队,相当于把元素element压入队
    element += copyLen;
    copyLen = queue->itemSize - copyLen;
    //对队列剩余可用空间进行统计
    if (copyLen > 0) {
    
    
        if (memcpy_s(queue->buffer, copyLen, element, copyLen) != EOK) {
    
    
            return EC_INVALID;
        }
    }
    //对所有写入元素进行统计
    uint32 write = queue->write + queue->itemSize;
    if (write >= queue->totalSize) {
    
    
    //如果write超过总地size大小,就做一次差,相当于取模
        write = write - queue->totalSize;
    }
    //
    queue->write = write;
    //队列write指针改为开始位置
    return EC_SUCCESS;
}

压队时候这里是有一定原子性,就是itemsize的空间是一个整体,如果空间足够就选择一整个item,如果不够就选择剩余空间。这里的write指针也如果超出总size就会向totalsize取模一次,保证步长为itemsize。
pop函数
出队的机制基本和入队类似,这里也是一个itemsize全部出队,将队列首部元素写进element,如果空间大于itemsize,则读出一整个itemsize,如果不足一个itemsize,就全部拿出。体现了一个原子性。个人理解!


int LFQUE_Pop(LockFreeQueue *queue, void *element, uint8 *pri)
{
    
    
    (void)pri;
    if (queue == NULL || element == NULL) {
    
    
        return EC_INVALID;
    }
    if (LFQUE_IsEmpty(queue)) {
    
    
        return EC_FAILURE;
    }

    uint32 copyLen = (queue->totalSize - queue->read < queue->itemSize) ?
                  (queue->totalSize - queue->read) : queue->itemSize;
    if (memcpy_s(element, copyLen, &queue->buffer[queue->read], copyLen) != EOK) {
    
    
        return EC_FAILURE;
    }

    element += copyLen;
    copyLen = queue->itemSize - copyLen;
    if (copyLen > 0) {
    
    
        if (memcpy_s(element, copyLen, queue->buffer, copyLen) != EOK) {
    
    
            return EC_FAILURE;
        }
    }

    uint32 read = queue->read + queue->itemSize;
    if (read >= queue->totalSize) {
    
    
        read = read - queue->totalSize;
    }
    queue->read = read;
    return EC_SUCCESS;
}

lock_free_quene队列是一个FIFO机制,是指无锁的队列。并发事务的处理有一定特殊性,没有锁的限制,全靠队列的机制实现。
无锁队列基本原理
在这里插入图片描述
感谢阅读!

猜你喜欢

转载自blog.csdn.net/m0_46976252/article/details/120563553