摘要:本文通过分析鸿蒙轻内核队列模块的源码,掌握队列使用上的差异。

本文分享自华为云社区《鸿蒙轻内核M核源码分析系列十三 消息队列Queue》,作者:zhushy 。

队列(Queue)是一种常用于任务间通信的数据结构。任务能够从队列里面读取消息,当队列中的消息为空时,挂起读取任务;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息。任务也能够往队列里写入消息,当队列已经写满消息时,挂起写入任务;当队列中有空闲消息节点时,挂起的写入任务被唤醒并写入消息。如果将读队列和写队列的超时时间设置为0,则不会挂起任务,接口会直接返回,这就是非阻塞模式。消息队列提供了异步处理机制,允许将一个消息放入队列,但不立即处理。同时队列还有缓冲消息的作用。

本文通过分析鸿蒙轻内核队列模块的源码,掌握队列使用上的差异。本文中所涉及的源码,以OpenHarmony LiteOS-M内核为例,均可以在开源站点https://gitee.com/openharmony/kernel_liteos_m 获取。

1、队列结构体定义和常用宏定义

1.1 队列结构体定义

在文件kernel\include\los_queue.h中定义队列控制块结构体为LosQueueCB,结构体源代码如下。队列状态.queueState取值OS_QUEUE_UNUSED、OS_QUEUE_INUSED,其他结构体成员见注释部分。

typedef struct {
UINT8 *queue; /**< 队列内存空间的指针 */
UINT16 queueState; /**< 队列的使用状态 */
UINT16 queueLen; /**< 队列长度,即消息数量 */
UINT16 queueSize; /**< 消息节点大小 */
UINT16 queueID; /**< 队列编号 */
UINT16 queueHead; /**< 消息头节点位置 */
UINT16 queueTail; /**< 消息尾节点位置 */
UINT16 readWriteableCnt[OS_READWRITE_LEN]; /**< 2维数组,可读、可写的消息数量, 0:可读, 1:可写 */
LOS_DL_LIST readWriteList[OS_READWRITE_LEN]; /**< 2维双向链表数组,阻塞读、写任务的双向链表, 0:读链表, 1:写链表 */
LOS_DL_LIST memList; /**< 内存节点双向链表 */
} LosQueueCB;

1.2 队列常用宏定义

系统支持创建多少队列是根据开发板情况使用宏LOSCFG_BASE_IPC_QUEUE_LIMIT定义的,每一个队列queueID是queueID类型的,取值为[0,LOSCFG_BASE_IPC_QUEUE_LIMIT),表示队列池中各个队列的编号。

⑴处的宏从队列池中获取指定队列编号QueueID对应的队列控制块。⑵处根据双向链表节点readWriteList[OS_QUEUE_WRITE]获取队列控制块内存地址。

⑴    #define GET_QUEUE_HANDLE(QueueID) (((LosQueueCB *)g_allQueue) + (QueueID))

⑵    #define GET_QUEUE_LIST(ptr) LOS_DL_LIST_ENTRY(ptr, LosQueueCB, readWriteList[OS_QUEUE_WRITE])

另外,队列中还提供了比较重要的队列读取消息操作相关的枚举和宏。枚举QueueReadWrite区分队列的读和写,枚举QueueHeadTail区分队列的首和尾,枚举QueuePointOrNot区分读写消息时是使用值还是指针。

队列的操作类型使用3比特位的数字来表示,见宏OS_QUEUE_OPERATE_TYPE的定义,其中高1位表示读写数值还是读写指针地址,中1位表示队首还是队尾,低1位表示读取还是写入。枚举和宏的定义如下:

typedef enum {
OS_QUEUE_READ,
OS_QUEUE_WRITE
} QueueReadWrite; typedef enum {
OS_QUEUE_HEAD,
OS_QUEUE_TAIL
} QueueHeadTail; typedef enum {
OS_QUEUE_NOT_POINT,
OS_QUEUE_POINT
} QueuePointOrNot; #define OS_QUEUE_OPERATE_TYPE(ReadOrWrite, HeadOrTail, PointOrNot) \
(((UINT32)(PointOrNot) << 2) | ((UINT32)(HeadOrTail) << 1) | (ReadOrWrite))
#define OS_QUEUE_READ_WRITE_GET(type) ((type) & (0x01))
#define OS_QUEUE_READ_HEAD (OS_QUEUE_READ | (OS_QUEUE_HEAD << 1))
#define OS_QUEUE_READ_TAIL (OS_QUEUE_READ | (OS_QUEUE_TAIL << 1))
#define OS_QUEUE_WRITE_HEAD (OS_QUEUE_WRITE | (OS_QUEUE_HEAD << 1))
#define OS_QUEUE_WRITE_TAIL (OS_QUEUE_WRITE | (OS_QUEUE_TAIL << 1))
#define OS_QUEUE_OPERATE_GET(type) ((type) & (0x03))
#define OS_QUEUE_IS_POINT(type) ((type) & (0x04))
#define OS_QUEUE_IS_READ(type) (OS_QUEUE_READ_WRITE_GET(type) == OS_QUEUE_READ)
#define OS_QUEUE_IS_WRITE(type) (OS_QUEUE_READ_WRITE_GET(type) == OS_QUEUE_WRITE)
#define OS_READWRITE_LEN 2

2、队列初始化

队列在内核中默认开启,用户可以通过宏LOSCFG_BASE_IPC_QUEUE进行关闭。开启队列的情况下,在系统启动时,在kernel\src\los_init.c中调用OsQueueInit()进行队列模块初始化。下面,我们分析下队列初始化的代码。

⑴为队列申请内存,如果申请失败,则返回错误。⑵初始化双向循环链表g_freeQueueList,维护未使用的队列。⑶循环每一个队列进行初始化,为每一个队列节点指定索引queueID,并把队列节点插入未使用队列双向链表g_freeQueueList。代码上可以看出,挂在未使用队列双向链表上的节点是每个队列控制块的写阻塞任务链表节点.readWriteList[OS_QUEUE_WRITE]。

LITE_OS_SEC_TEXT_INIT UINT32 OsQueueInit(VOID)
{
LosQueueCB *queueNode = NULL;
UINT16 index; if (LOSCFG_BASE_IPC_QUEUE_LIMIT == 0) {
return LOS_ERRNO_QUEUE_MAXNUM_ZERO;
} ⑴ g_allQueue = (LosQueueCB *)LOS_MemAlloc(m_aucSysMem0, LOSCFG_BASE_IPC_QUEUE_LIMIT * sizeof(LosQueueCB));
if (g_allQueue == NULL) {
return LOS_ERRNO_QUEUE_NO_MEMORY;
} (VOID)memset_s(g_allQueue, LOSCFG_BASE_IPC_QUEUE_LIMIT * sizeof(LosQueueCB),
0, LOSCFG_BASE_IPC_QUEUE_LIMIT * sizeof(LosQueueCB)); ⑵ LOS_ListInit(&g_freeQueueList);
⑶ for (index = 0; index < LOSCFG_BASE_IPC_QUEUE_LIMIT; index++) {
queueNode = ((LosQueueCB *)g_allQueue) + index;
queueNode->queueID = index;
LOS_ListTailInsert(&g_freeQueueList, &queueNode->readWriteList[OS_QUEUE_WRITE]);
} return LOS_OK;
}

3、队列常用操作

3.1 队列创建

创建队列函数是LOS_QueueCreate(),先看看该函数的参数:queueName是队列名称,实际上并没有使用。len是队列中消息的数量,queueID是队列编号,flags保留未使用。maxMsgSize是队列中每条消息的最大大小。

我们分析下创建队列的代码。⑴处对参数进行校验,队列编码不能为空,队列消息长度不能太大,队列消息数量和队列消息大小不能为0。⑵处计算消息的实际最大大小msgSize,即maxMsgSize + sizeof(UINT32)消息最大大小再加4个字节,在消息的最后4个字节用来保存消息的实际长度。然后调用⑶处函数LOS_MemAlloc()为对队列动态申请内存,如果内存申请失败,则返回错误码。

⑷处判断g_freeQueueList是否为空,如果没有可以使用的队列,释放前文申请的内存。⑸处如果g_freeQueueList不为空,则获取第一个可用的队列节点,接着从双向链表g_freeQueueList中删除,然后调用宏GET_QUEUE_LIST获取LosQueueCB *queueCB,初始化创建的队列信息,包含队列的长度.queueLen、消息大小.queueSize,队列内存空间.queue,消息状态.queueState,可读的数量.readWriteableCnt[OS_QUEUE_READ]为0,可写的数量readWriteableCnt[OS_QUEUE_WRITE]为队列消息长度len,队列头位置.queueHead和尾位置.queueTail为0。

⑹初始化双向链表.readWriteList[OS_QUEUE_READ],阻塞在这个队列上的读消息任务会挂在这个链表上。初始化双向链表.readWriteList[OS_QUEUE_WRITE],阻塞在这个队列上的写消息任务会挂在这个链表上。初始化双向链表.memList。⑺赋值给输出参数*queueID,后续程序使用这个队列编号对队列进行其他操作。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueCreate(CHAR *queueName,
UINT16 len,
UINT32 *queueID,
UINT32 flags,
UINT16 maxMsgSize)
{
LosQueueCB *queueCB = NULL;
UINT32 intSave;
LOS_DL_LIST *unusedQueue = NULL;
UINT8 *queue = NULL;
UINT16 msgSize; (VOID)queueName;
(VOID)flags; ⑴ if (queueID == NULL) {
return LOS_ERRNO_QUEUE_CREAT_PTR_NULL;
} if (maxMsgSize > (OS_NULL_SHORT - sizeof(UINT32))) {
return LOS_ERRNO_QUEUE_SIZE_TOO_BIG;
} if ((len == 0) || (maxMsgSize == 0)) {
return LOS_ERRNO_QUEUE_PARA_ISZERO;
}
⑵ msgSize = maxMsgSize + sizeof(UINT32); /* Memory allocation is time-consuming, to shorten the time of disable interrupt,
move the memory allocation to here. */
⑶ queue = (UINT8 *)LOS_MemAlloc(m_aucSysMem0, len * msgSize);
if (queue == NULL) {
return LOS_ERRNO_QUEUE_CREATE_NO_MEMORY;
} intSave = LOS_IntLock();
⑷ if (LOS_ListEmpty(&g_freeQueueList)) {
LOS_IntRestore(intSave);
(VOID)LOS_MemFree(m_aucSysMem0, queue);
return LOS_ERRNO_QUEUE_CB_UNAVAILABLE;
} ⑸ unusedQueue = LOS_DL_LIST_FIRST(&(g_freeQueueList));
LOS_ListDelete(unusedQueue);
queueCB = (GET_QUEUE_LIST(unusedQueue));
queueCB->queueLen = len;
queueCB->queueSize = msgSize;
queueCB->queue = queue;
queueCB->queueState = OS_QUEUE_INUSED;
queueCB->readWriteableCnt[OS_QUEUE_READ] = 0;
queueCB->readWriteableCnt[OS_QUEUE_WRITE] = len;
queueCB->queueHead = 0;
queueCB->queueTail = 0;
⑹ LOS_ListInit(&queueCB->readWriteList[OS_QUEUE_READ]);
LOS_ListInit(&queueCB->readWriteList[OS_QUEUE_WRITE]);
LOS_ListInit(&queueCB->memList);
LOS_IntRestore(intSave); ⑺ *queueID = queueCB->queueID; OsHookCall(LOS_HOOK_TYPE_QUEUE_CREATE, queueCB); return LOS_OK;
}

3.2 队列删除

我们可以使用函数LOS_QueueDelete(UINT32 queueID)来删除队列,下面通过分析源码看看如何删除队列的。

⑴处判断队列queueID是否超过LOSCFG_BASE_IPC_QUEUE_LIMIT,如果超过则返回错误码。如果队列编号没有问题,获取队列控制块LosQueueCB *queueCB。⑵处判断要删除的队列处于未使用状态,则跳转到错误标签QUEUE_END进行处理。⑶如果队列的阻塞读、阻塞写任务列表不为空,或内存节点链表不为空,则不允许删除,跳转到错误标签进行处理。⑷处检验队列的可读、可写数量是否出错。

⑸处使用指针UINT8 *queue保存队列的内存空间,⑹处把.queue置空,把.queueState设置为未使用OS_QUEUE_UNUSED,并把队列节点插入未使用队列双向链表g_freeQueueList。接下来会需要调用⑺处函数LOS_MemFree()释放队列内存空间。

LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueDelete(UINT32 queueID)
{
LosQueueCB *queueCB = NULL;
UINT8 *queue = NULL;
UINT32 intSave;
UINT32 ret; ⑴ if (queueID >= LOSCFG_BASE_IPC_QUEUE_LIMIT) {
return LOS_ERRNO_QUEUE_NOT_FOUND;
} intSave = LOS_IntLock();
queueCB = (LosQueueCB *)GET_QUEUE_HANDLE(queueID);
⑵ if (queueCB->queueState == OS_QUEUE_UNUSED) {
ret = LOS_ERRNO_QUEUE_NOT_CREATE;
goto QUEUE_END;
} ⑶ if (!LOS_ListEmpty(&queueCB->readWriteList[OS_QUEUE_READ])) {
ret = LOS_ERRNO_QUEUE_IN_TSKUSE;
goto QUEUE_END;
} if (!LOS_ListEmpty(&queueCB->readWriteList[OS_QUEUE_WRITE])) {
ret = LOS_ERRNO_QUEUE_IN_TSKUSE;
goto QUEUE_END;
} if (!LOS_ListEmpty(&queueCB->memList)) {
ret = LOS_ERRNO_QUEUE_IN_TSKUSE;
goto QUEUE_END;
} ⑷ if ((queueCB->readWriteableCnt[OS_QUEUE_WRITE] + queueCB->readWriteableCnt[OS_QUEUE_READ]) !=
queueCB->queueLen) {
ret = LOS_ERRNO_QUEUE_IN_TSKWRITE;
goto QUEUE_END;
} ⑸ queue = queueCB->queue;
⑹ queueCB->queue = (UINT8 *)NULL;
queueCB->queueState = OS_QUEUE_UNUSED;
LOS_ListAdd(&g_freeQueueList, &queueCB->readWriteList[OS_QUEUE_WRITE]);
LOS_IntRestore(intSave); OsHookCall(LOS_HOOK_TYPE_QUEUE_DELETE, queueCB); ⑺ ret = LOS_MemFree(m_aucSysMem0, (VOID *)queue);
return ret; QUEUE_END:
LOS_IntRestore(intSave);
return ret;
}

下面就来看看队列的读写,有2点需要注意:

  • 队首、队尾的读写

只支持队首读取,不能队尾读取,否则就不算队列了。除了正常的队尾写消息外,还提供插队机制,支持从队首写入。

  • 队列消息数据内容

往队列中写入的消息的类型有2种,即支持按地址写入和按值写入(带拷贝)。按哪种类型写入,就需要配对的按相应的类型去读取。

队列读取接口的类别,归纳如下:

3.3 队列读取

我们知道有2个队列读取方法,按指针地址读取的函数LOS_QueueRead()和按消息数值读取的函数LOS_QueueReadCopy()。我们先看下函数LOS_QueueRead(),该函数的参数有4个,队列编号queueID,存放读取到的消息的缓冲区地址*bufferAddr,存放读取到的消息的缓冲区大小bufferSize,读队列消息的等待超时时间timeOut。代码如下,我们分析下代码。

⑴处校验传入参数,队列编号不能超出限制,传入的指针不能为空,缓冲大小不能为0。如果timeout不为零,不能在中断中读取队列。⑵处操作类型表示队首读取消息指针,然后调用函数OsQueueOperate()进一步操作队列。

LITE_OS_SEC_TEXT UINT32 LOS_QueueRead(UINT32 queueID, VOID *bufferAddr, UINT32 bufferSize, UINT32 timeOut)
{
UINT32 ret;
UINT32 operateType; ⑴ ret = OsQueueReadParameterCheck(queueID, bufferAddr, &bufferSize, timeOut);
if (ret != LOS_OK) {
return ret;
} ⑵ operateType = OS_QUEUE_OPERATE_TYPE(OS_QUEUE_READ, OS_QUEUE_HEAD, OS_QUEUE_POINT); OsHookCall(LOS_HOOK_TYPE_QUEUE_READ, (LosQueueCB *)GET_QUEUE_HANDLE(queueID)); return OsQueueOperate(queueID, operateType, bufferAddr, &bufferSize, timeOut);
}

我们进一步分析下函数OsQueueOperate(),这是是比较通用的封装,读取,写入都会调用这个函数,我们以读取队列为例分析这个函数。⑴处获取队列的操作类型,为读取操作。⑵处先调用函数OsQueueOperateParamCheck()进行参数校验,校验队列是使用中的队列,并对读写消息大小进行校验。⑶处如果可读数量为0,无法读取时,如果是零等待则返回错误码。如果当前锁任务调度,跳出函数执行。否则,执行⑷把当前任务放入队列的读取消息阻塞队列,然后触发任务调度,后续的代码暂时不再执行。如果可读的数量不为0,可以继续读取时,执行⑹处代码把可读数量减1,然后继续执行⑺处代码读取队列。

等读取队列阻塞超时,或者队列可以读取后,继续执行⑸处的代码。如果是发生超时,队列还不能读取,更改任务状态,跳出函数执行。如果队列可以读取了,继续执行⑺处代码读取队列。⑻处在成功读取队列后,如果有任务阻塞在写入队列,则获取阻塞链表中的第一个任务resumedTask,然后调用唤醒函数OsSchedTaskWake()把待恢复的任务放入就绪队列,触发一次任务调度。如果无阻塞任务,则把可写入的数量加1。

UINT32 OsQueueOperate(UINT32 queueID, UINT32 operateType, VOID *bufferAddr, UINT32 *bufferSize, UINT32 timeOut)
{
LosQueueCB *queueCB = NULL;
LosTaskCB *resumedTask = NULL;
UINT32 ret;
⑴ UINT32 readWrite = OS_QUEUE_READ_WRITE_GET(operateType);
UINT32 readWriteTmp = !readWrite; UINT32 intSave = LOS_IntLock(); queueCB = (LosQueueCB *)GET_QUEUE_HANDLE(queueID);
⑵ ret = OsQueueOperateParamCheck(queueCB, operateType, bufferSize);
if (ret != LOS_OK) {
goto QUEUE_END;
} ⑶ if (queueCB->readWriteableCnt[readWrite] == 0) {
if (timeOut == LOS_NO_WAIT) {
ret = OS_QUEUE_IS_READ(operateType) ? LOS_ERRNO_QUEUE_ISEMPTY : LOS_ERRNO_QUEUE_ISFULL;
goto QUEUE_END;
} if (g_losTaskLock) {
ret = LOS_ERRNO_QUEUE_PEND_IN_LOCK;
goto QUEUE_END;
} LosTaskCB *runTsk = (LosTaskCB *)g_losTask.runTask;
⑷ OsSchedTaskWait(&queueCB->readWriteList[readWrite], timeOut);
LOS_IntRestore(intSave);
LOS_Schedule(); intSave = LOS_IntLock();
⑸ if (runTsk->taskStatus & OS_TASK_STATUS_TIMEOUT) {
runTsk->taskStatus &= ~OS_TASK_STATUS_TIMEOUT;
ret = LOS_ERRNO_QUEUE_TIMEOUT;
goto QUEUE_END;
}
} else {
⑹ queueCB->readWriteableCnt[readWrite]--;
} ⑺ OsQueueBufferOperate(queueCB, operateType, bufferAddr, bufferSize);
⑻ if (!LOS_ListEmpty(&queueCB->readWriteList[readWriteTmp])) {
resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&queueCB->readWriteList[readWriteTmp]));
OsSchedTaskWake(resumedTask);
LOS_IntRestore(intSave);
LOS_Schedule();
return LOS_OK;
} else {
⑼ queueCB->readWriteableCnt[readWriteTmp]++;
} QUEUE_END:
LOS_IntRestore(intSave);
return ret;
}

我们再继续看下函数OsQueueBufferOperate()是具体如何读取队列的。⑴处switch-case语句根据操作类型获取操作位置。对于⑵头部读取的情况,先获取读取位置queuePosition。然后,如果当前头节点位置.queueHead加1等于队列消息长度,头节点位置.queueHead设置为0,否则加1。对于⑶头部写入的情况,如果当前头节点位置.queueHead等于0,头节点位置.queueHead设置为队列消息长度减1即queueCB->queueLen - 1,否则头节点位置.queueHead减1即可。然后,获取要写入的位置queuePosition。对于⑷尾部写入的情况,先获取写入位置queuePosition。然后,如果当前尾节点位置.queueTail加1等于队列消息长度,尾节点位置.queueTail设置为0,否则加1。

⑸处基于获取的队列读取位置获取队列消息节点queueNode。⑹处判断操作类型如果是按指针读写消息,直接读取消息节点的数据写入指针对应的缓冲区*(UINT32 *)bufferAddr,或直接把指针对应的缓冲区*(UINT32 *)bufferAddr数据写入消息节点即可。我们接着看如何按数数据读写消息,⑺处代码用于读取数据消息。每个消息节点的后4个字节保存的是消息的长度,首先获取消息的长度msgDataSize,然后把消息内容读取到bufferAddr。再看看⑻处如何写入队列消息,首先把消息内容写入到queueNode,然后再把消息长度的内容写入到queueNode + queueCB->queueSize - sizeof(UINT32),就是每个消息节点的后4字节。

static INLINE VOID OsQueueBufferOperate(LosQueueCB *queueCB, UINT32 operateType,
VOID *bufferAddr, UINT32 *bufferSize)
{
UINT8 *queueNode = NULL;
UINT32 msgDataSize;
UINT16 queuePosion;
errno_t rc; /* get the queue position */
⑴ switch (OS_QUEUE_OPERATE_GET(operateType)) {
case OS_QUEUE_READ_HEAD:
⑵ queuePosion = queueCB->queueHead;
((queueCB->queueHead + 1) == queueCB->queueLen) ? (queueCB->queueHead = 0) : (queueCB->queueHead++);
break; case OS_QUEUE_WRITE_HEAD:
⑶ (queueCB->queueHead == 0) ? (queueCB->queueHead = (queueCB->queueLen - 1)) : (--queueCB->queueHead);
queuePosion = queueCB->queueHead;
break; case OS_QUEUE_WRITE_TAIL:
⑷ queuePosion = queueCB->queueTail;
((queueCB->queueTail + 1) == queueCB->queueLen) ? (queueCB->queueTail = 0) : (queueCB->queueTail++);
break; default:
PRINT_ERR("invalid queue operate type!\n");
return;
} ⑸ queueNode = &(queueCB->queue[(queuePosion * (queueCB->queueSize))]); ⑹ if (OS_QUEUE_IS_POINT(operateType)) {
if (OS_QUEUE_IS_READ(operateType)) {
*(UINT32 *)bufferAddr = *(UINT32 *)(VOID *)queueNode;
} else {
*(UINT32 *)(VOID *)queueNode = *(UINT32 *)bufferAddr; // change to pp when calling OsQueueOperate
}
} else {
⑺ if (OS_QUEUE_IS_READ(operateType)) {
msgDataSize = *((UINT32 *)(UINTPTR)((queueNode + queueCB->queueSize) - sizeof(UINT32)));
rc = memcpy_s((VOID *)bufferAddr, *bufferSize, (VOID *)queueNode, msgDataSize);
if (rc != EOK) {
PRINT_ERR("%s[%d] memcpy failed, error type = %u\n", __FUNCTION__, __LINE__, rc);
return;
} *bufferSize = msgDataSize;
} else {
⑻ *((UINT32 *)(UINTPTR)((queueNode + queueCB->queueSize) - sizeof(UINT32))) = *bufferSize;
rc = memcpy_s((VOID *)queueNode, queueCB->queueSize, (VOID *)bufferAddr, *bufferSize);
if (rc != EOK) {
PRINT_ERR("%s[%d] memcpy failed, error type = %u\n", __FUNCTION__, __LINE__, rc);
return;
}
}
}
}

3.4 队列写入

我们知道,有4个队列写入方法,2个队尾写入,2个队首写入,分别包含按指针地址写入消息和按数值写入消息。LOS_QueueWrite()会调用LOS_QueueWriteCopy(),LOS_QueueWriteHead()会调用LOS_QueueWriteHeadCopy(),然后指定不同的操作类型后,会进一步调用前文已经分析过的函数OsQueueOperate()。

小结

本文带领大家一起剖析了鸿蒙轻内核的队列模块的源代码,包含队列的结构体、队列池初始化、队列创建删除、读写消息等。感谢阅读,如有任何问题、建议,都可以留言给我们: https://gitee.com/openharmony/kernel_liteos_m/issues 。为了更容易找到鸿蒙轻内核代码仓,建议访问 https://gitee.com/openharmony/kernel_liteos_m ,关注Watch、点赞Star、并Fork到自己账户下,谢谢。

点击关注,第一时间了解华为云新鲜技术~

队列Queue:任务间的消息读写,安排起来~的更多相关文章

  1. 使用队列queue实现一个简单的生产者消费者模型

    一.生产者消费者模型 我们去超市商店等地购买商品时,我们大部分人都会说自己是消费者,而超市的各大供货商.工厂等,自然而然地也就成了我们的生产者.如此一来,生产者有了,消费者也有了,那么将二者联系起来的 ...

  2. 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 百篇博客分析OpenHarmony源码 | v33.02

    百篇博客系列篇.本篇为: v33.xx 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁 ...

  3. Python进阶【第二篇】多线程、消息队列queue

    1.Python多线程.多进程 目的提高并发 1.一个应用程序,可以有多进程和多线程 2.默认:单进程,单线程 3.单进程,多线程 IO操作,不占用CPU python的多线程:IO操作,多线程提供并 ...

  4. python多进程之间的通信:消息队列Queue

    python中进程的通信:消息队列. 我们知道进程是互相独立的,各自运行在自己独立的内存空间. 所以进程之间不共享任何变量. 我们要想进程之间互相通信,传送一些东西怎么办? 需要用到消息队列!! 进程 ...

  5. 消息队列Queue大全

    消息队列Queue大全 (http://queues.io/) 作业队列,消息队列和其他队列.几乎所有你能想到的都在这. 关于 那里有很多排队系统.他们每个人都不同,是为解决某些问题而创建的.这个页面 ...

  6. 消息队列queue

    一.queue 在多线程编程中,程序的解耦往往是一个麻烦的问题,以及在socket网络编程中也会有这样的问题.recv 和send之间,如果服务端有消息,问题需要发送给客户端,而那边的recv 被主程 ...

  7. python消息队列Queue

    实例1:消息队列Queue,不要将文件命名为"queue.py",否则会报异常"ImportError: cannot import name 'Queue'" ...

  8. (七)RabbitMQ消息队列-通过fanout模式将消息推送到多个Queue中

    原文:(七)RabbitMQ消息队列-通过fanout模式将消息推送到多个Queue中 前面第六章我们使用的是direct直连模式来进行消息投递和分发.本章将介绍如何使用fanout模式将消息推送到多 ...

  9. 多线程多进程学习threading,queue线程安全队列,线程间数据状态读取。threading.local() threading.RLock()

    http://www.cnblogs.com/alex3714/articles/5230609.html python的多线程是通过上下文切换实现的,只能利用一核CPU,不适合CPU密集操作型任务, ...

随机推荐

  1. 35 张图带你 MySQL 调优

    这是 MySQL 基础系列的第四篇文章,之前的三篇文章见如下链接 138 张图带你 MySQL 入门 47 张图带你 MySQL 进阶!!! 炸裂!MySQL 82 张图带你飞 一般传统互联网公司很少 ...

  2. Java真的是白天鹅

    前言 我最近越来越真切的感受到,Java真的是白天鹅. 这真的是一种羡慕嫉妒恨的感受. 今天和一个Java技术Leader聊天,我告诉他敏捷开发是以人为本,他居然跟我说敏捷开发在行业内有规范,规范是死 ...

  3. Handler_read_*的总结

    在分析一个SQL的性能好坏时,除了执行计划,另外一个常看的指标是"Handler_read_*"相关变量. Handler_read_key Handler_read_first ...

  4. NCF 如何导入Excel数据

    简介 学了上一节的WebApi之后,我们会发现一片新天地 本节跟大家聊一聊,如何把本地的Excel数据导入到NCF中 仓库地址:https://github.com/NeuCharFramework/ ...

  5. yum install php-bcmath-5.4.16-42.el7.x86_64.rpm安装报错

    [root@zabbixserver lnmp_soft]# yum install php-bcmath-5.4.16-42.el7.x86_64.rpm 已加载插件:langpacks, prod ...

  6. gRPC趁现在还没大火,抢先了解一下

    前言 系统分布式已经成为程序员的家常,将大型单体划分为相对简单的小模块,分散系统能力,提升系统扩展性.功能模块复用性等:各功能模块之间肯定会有很多数据共享和交互的应用场景,那就避免不了各模块之间的通信 ...

  7. 在vue中下拉框切换事件中改新建表单中的一个值,页面不显示

    事件中改新建表单中的一个值,页面不显示,当另一个对象值发生改变时,这个页面上的值才会显示 由于新建表单是弹窗,在弹出时会重新给每个字段重新赋值,在赋值时没给这个字段赋值(常见新加功能时,加了一个字段, ...

  8. 基于 electron 实现简单易用的抓包、mock 工具

    背景 经常我们要去看一些页面所发出的请求时,经常会用到 Charles 做为抓包工具来进行接口抓取,但一方面市面是很多抓包工具都是收费或者无法二次开发的.当前我们团队大多数用的也都是 Charles, ...

  9. react的三大属性

    react的三大属性 state props  refs props 来自外部属性 states 来自内部状态 refs 用于表示组件内某个元素 state基础(最重要的属性) state是组件对象最 ...

  10. Unity项目代码书写规范

    以Google的代码规范为主,稍加改动 https://google.github.io/styleguide/csharp-style.html 书写规范 基础写法 Pascal和驼峰混用,参数用驼 ...