【HarmonyOS源码学习系列】队列

1,593 阅读4分钟

看代码的涉及风格是面向对象设计。先设计接口,然后不同的平台去做对应的实现。

接口定义

code-1.0/foundation/distributedschedule/services/samgr_lite/samgr/adapter/queue_adapter.h

MQueueId QUEUE_Create(const char *name, int size, int count);

int QUEUE_Put(MQueueId queueId, const void *element, uint8 pri, int timeout);

int QUEUE_Pop(MQueueId queueId, void *element, uint8 *pri, int timeout);

int QUEUE_Destroy(MQueueId queueId);

队列定义了四个接口,分别是创建队列、添加元素到队列、从队列取出元素、销毁队列。

队列的实现上涉及两个概念无锁队列(LockFreeQueue)和加锁队列(LockFreeBlockQueue)。

LockFreeBlockQueue是在LockFreeQueue的基础上增加锁。

LockFreeQueue

code-1.0/foundation/distributedschedule/services/samgr_lite/samgr/adapter/posix/lock_free_queue.h

struct LockFreeQueue {
    uint32 write;       // 写游标
    uint32 read;        // 读游标
    uint32 itemSize;    // 一个元素的大小
    uint32 totalSize;   // 缓冲区大小
    uint8 buffer[0];    // 缓冲区指针
};

LockFreeQueue *LFQUE_Create(int size, int count);

BOOL LFQUE_IsEmpty(LockFreeQueue *queue);

BOOL LFQUE_IsFull(LockFreeQueue *queue);

int LFQUE_Push(LockFreeQueue *queue, const void *element, uint8 pri);

int LFQUE_Pop(LockFreeQueue *queue, void *element, uint8 *pri);

LockFreeQueue数据结构定义和接口定义。定义接口:创建队列、获取队列是否空、获取队列是否满、元素入队列、元素出队列。

code-1.0/foundation/distributedschedule/services/samgr_lite/samgr/adapter/posix/lock_free_queue.c

LockFreeQueue创建

// 传入参数:元素的大小和个数
LockFreeQueue *LFQUE_Create(int size, int count)
{
    // 计算buffer总大小
    // 为啥还要加1?
    int total = size * count + 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;
    // 设置buffer总大小
    queue->totalSize = total;
    return queue;
}

计算buffer时,多加了个1,是因为:队列设计需要。队列设计的一条是:写入一个元素后,写游标移动一个元素的长度。读写游标重合表示队列空,写游标的下一个位置是读游标表示队列满。读游标永远指向队列数据的开始位置,写游标永远位于队列末尾数据的下一个位置。即,写游标永远指向一个未写入数据的位置。所以buffer的长度要在总元素长度的基础上加一个写游标的位置(1个字节)。

判断队列是空还是满

BOOL LFQUE_IsFull(LockFreeQueue *queue)
{
    uint32 nextWrite = queue->write + 1;
    if (nextWrite >= queue->totalSize) {
        nextWrite = 0;
    }
    return (nextWrite == queue->read);
}

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

如果下一次写的位置和读的位置是同一个位置,则队列满。

如果写的位置和读的位置是同一个位置,则队列空。

入队列

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 += 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 = write - queue->totalSize;
    }
    queue->write = write;
    return EC_SUCCESS;
}

缓冲区是作为环形缓冲区使用,但是实际是一段内存,并非环形。写入数据的时候,要考虑是否写入到了这段内存的末尾,如果不够用,需要把剩余的部分写入到这段内存的开头,所以会出现上面比较多的代码操作元素写入。

出队列

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;
}

一段内存模拟的环形缓冲区,这里和元素入队列类似,都会涉及到达内存末端的情况。

LockFreeBlockQueue

code-1.0/foundation/distributedschedule/services/samgr_lite/samgr/adapter/posix/queue_adapter.c

struct LockFreeBlockQueue {
    pthread_mutex_t wMutex;
    pthread_mutex_t rMutex;
    pthread_cond_t cond;
    LockFreeQueue *queue;
};

LockFreeQueue的基础上增加了读写信号锁和一个条件变量。

创建LockFreeBlockQueue

MQueueId QUEUE_Create(const char *name, int size, int count)
{
    // 分配内存
    LockFreeBlockQueue *queue = (LockFreeBlockQueue *)SAMGR_Malloc(sizeof(LockFreeBlockQueue));
    if (queue == NULL) {
        return NULL;
    }
    // 创建LFQ,并赋值queue
    queue->queue = LFQUE_Create(size, count);
    if (queue->queue == NULL) {
        SAMGR_Free(queue);
        return NULL;
    }
    // 读写信号所初始化
    pthread_mutex_init(&queue->wMutex, NULL);
    pthread_mutex_init(&queue->rMutex, NULL);
    // 条件变量初始化
    pthread_cond_init(&queue->cond, NULL);
    return (MQueueId)queue;
}

入队列

int QUEUE_Put(MQueueId queueId, const void *element, uint8 pri, int timeout)
{
    if (queueId == NULL || element == NULL || timeout > 0) {
        return EC_INVALID;
    }
    // 类型强转,void*类型转为LockFreeBlockQueue*类型
    LockFreeBlockQueue *queue = (LockFreeBlockQueue *)queueId;
    // 锁定wMutex
    pthread_mutex_lock(&queue->wMutex);
    // 将元素写入队列
    int ret = LFQUE_Push(queue->queue, element, pri);
    // 释放wMutex
    pthread_mutex_unlock(&queue->wMutex);
    // 锁定rMutex
    pthread_mutex_lock(&queue->rMutex);
    // 唤醒条件变量cond上的线程
    pthread_cond_signal(&queue->cond);
    // 释放rMutex
    pthread_mutex_unlock(&queue->rMutex);
    return ret;
}

出队列

int QUEUE_Pop(MQueueId queueId, void *element, uint8 *pri, int timeout)
{
    if (queueId == NULL || element == NULL || timeout > 0) {
        return EC_INVALID;
    }
    // 类型强转,void*类型转为LockFreeBlockQueue*类型
    LockFreeBlockQueue *queue = (LockFreeBlockQueue *)queueId;
    // 读取元素,这段代码的前后为什么不要操作rMutex
    if (LFQUE_Pop(queue->queue, element, pri) == EC_SUCCESS) {
        return EC_SUCCESS;
    }
    // 锁定rMutex
    pthread_mutex_lock(&queue->rMutex);
    // 如果读取元素不成功,说明队列空,进入等待状态
    while (LFQUE_Pop(queue->queue, element, pri) != EC_SUCCESS) {
        pthread_cond_wait(&queue->cond, &queue->rMutex);
    }
    // 释放rMutex
    pthread_mutex_unlock(&queue->rMutex);
    return EC_SUCCESS;
}

销毁队列

int QUEUE_Destroy(MQueueId queueId)
{
    if (queueId == NULL) {
        return EC_INVALID;
    }
    // 类型强转,void*类型转为LockFreeBlockQueue*类型
    LockFreeBlockQueue *queue = (LockFreeBlockQueue *)queueId;
    // 销毁读写信号锁和条件变量
    pthread_mutex_destroy(&queue->wMutex);
    pthread_mutex_destroy(&queue->rMutex);
    pthread_cond_destroy(&queue->cond);
    // 释放LFQ
    SAMGR_Free(queue->queue);
    // 释放队列指针
    SAMGR_Free(queue);
    return EC_SUCCESS;
}

LockFreeBlockQueue的内存模型