FreeRTOS 的一个重要的通信机制----消息队列,消息队列在实际项目中应用较多。

一.消息队列的作用及概念:

消息队列就是通过 RTOS 内核提供的服务,任务或中断服务子程序可以将一个消息(注意,FreeRTOS消息队列传递的是实际数据,并不是数据地址,RTX,uCOS-II 和 uCOS-III 是传递的地址)放入到队列。同样,一个或者多个任务可以通过 RTOS 内核服务从队列中得到消息。通常,先进入消息队列的消息先传给任务,也就是说,任务先得到的是最先进入到消息队列的消息,即先进先出的原则(FIFO),FreeRTOS的消息队列支持 FIFO 和 LIFO 两种数据存取方式。

也许有不理解的初学者会问采用消息队列多麻烦,搞个全局数组不是更简单,其实不然。在裸机编程时,使用全局数组的确比较方便,但是在加上 RTOS 后就是另一种情况了。相比消息队列,使用全局数组主要有如下四个问题:

  1. 使用消息队列可以让 RTOS 内核有效地管理任务,而全局数组是无法做到的,任务的超时等机制需要用户自己去实现。
  2. 使用了全局数组就要防止多任务的访问冲突,而使用消息队列则处理好了这个问题,用户无需担心。
  3. 使用消息队列可以有效地解决中断服务程序与任务之间消息传递的问题。
  4. FIFO 机制更有利于数据的处理。

二. FreeRTOS 任务间消息队列的实现:

任务间消息队列的实现是指各个任务之间使用消息队列实现任务间的通信。下面我们通过如下的框图来说明一下 FreeRTOS 消息队列的实现,让大家有一个形象的认识。

运行条件:

  1. 创建消息队列,可以存放 10 个消息。
  2. 创建 2 个任务 Task1 和 Task2,任务 Task1 向消息队列放数据,任务 Task2 从消息队列取数据。
  3. FreeRTOS 的消息存取采用 FIFO 方式。

运行过程主要有以下两种情况:

  1. 任务 Task1 向消息队列放数据,任务 Task2 从消息队列取数据,如果放数据的速度快于取数据的速度,那么会出现消息队列存放满的情况,FreeRTOS 的消息存放函数 xQueueSend 支持超时等待,用户可以设置超时等待,直到有空间可以存放消息或者设置的超时时间溢出。
  2. 任务 Task1 向消息队列放数据,任务 Task2 从消息队列取数据,如果放数据的速度慢于取数据的速度,那么会出现消息队列为空的情况,FreeRTOS 的消息获取函数 xQueueReceive 支持超时等待,用户可以设置超时等待,直到消息队列中有消息或者设置的超时时间溢出。

  上面就是一个简单的 FreeRTOS 任务间消息队列通信过程。

三.FreeRTOS 中断方式消息队列的实现:

FreeRTOS 中断方式消息队列的实现是指中断函数和 FreeRTOS 任务之间使用消息队列。下面我们通过如下的框图来说明一下 FreeRTOS 消息队列的实现,让大家有一个形象的认识。

运行条件:

  1. 创建消息队列,可以存放 10 个消息。
  2. 创建 1 个任务 Task1 和一个串口接收中断。
  3. FreeRTOS 的消息存取采用 FIFO 方式。

运行过程主要有以下两种情况:

  1. 中断服务程序向消息队列放数据,任务 Task1 从消息队列取数据,如果放数据的速度快于取数据的速度,那么会出现消息队列存放满的情况。由于中断服务程序里面的消息队列发送函数xQueueSendFromISR 不支持超时设置,所以发送前要通过函数 xQueueIsQueueFullFromISR 检测消息队列是否满。
  2. 中断服务程序向消息队列放数据,任务 Task1 从消息队列取数据,如果放数据的速度慢于取数据的速度,那么会出现消息队列存为空的情况。在 FreeRTOS 的任务中可以通过函数 xQueueReceive 获取消息,因为此函数可以设置超时等待,直到消息队列中有消息存放或者设置的超时时间溢出。

  上面就是一个简单的 FreeRTOS 中断方式消息队列通信过程。实际应用中,中断方式的消息机制要注意以下四个问题:

  1. 中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
  2. 实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优先级,以便退出中断函数后任务可以得到及时执行。
  3. 中断服务程序中一定要调用专用于中断的消息队列函数,即以 FromISR 结尾的函数。
  4. 在操作系统中实现中断服务程序与裸机编程的区别。
    • 如果 FreeRTOS 工程的中断函数中没有调用 FreeRTOS 的消息队列 API 函数,与裸机编程是一样的。
    • 如果 FreeRTOS 工程的中断函数中调用了 FreeRTOS 的消息队列的 API 函数,退出的时候要检测是否有高优先级任务就绪,如果有就绪的,需要在退出中断后进行任务切换,这点与裸机编程稍有区别,详见 实验例程说明(中断方式):
    • 另外强烈推荐用户将 Cortex-M3 内核的 STM32F103 和 Cortex-M4 内核的 STM32F407,F429的 NVIC 优先级分组设置为 4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);这样中断优先级的管理将非常方便。
    • 用户要在 FreeRTOS 多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。

四.消息队列API函数,使用如下 23 个函数可以实现 FreeRTOS 的消息队列:

  1.  xQueueCreateStatic()
  2.  vQueueDelete()
  3.  xQueueSend()
  4.  xQueueSendFromISR()
  5.  xQueueSendToBack()
  6.  xQueueSendToBackFromISR()
  7.  xQueueSendToFront()
  8.  xQueueSendToFrontFromISR()
  9.  xQueueReceive()
  10.  xQueueReceiveFromISR()
  11.  uxQueueMessagesWaiting()
  12.  uxQueueMessagesWaitingFromISR()
  13.  uxQueueSpacesAvailable()
  14.  xQueueReset()
  15.  xQueueOverwrite()
  16.  xQueueOverwriteFromISR()
  17.  xQueuePeek()
  18.  xQueuePeekFromISR()
  19.  vQueueAddToRegistry()
  20.  vQueueUnregisterQueue()
  21.  pcQueueGetName()
  22.  xQueueIsQueueFullFromISR()
  23.  xQueueIsQueueEmptyFromISR()

关于这 23 个函数的讲解及其使用方法可以看 FreeRTOS 在线版手册,这里重点的说以下 4 个函数:

  • xQueueCreate ()
  • xQueueSend ()
  • xQueueSendFromISR ()
  • xQueueReceive ()

    1.1 函 数 xQueueCreate

函数原型:
    QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, /* 消息个数 */
               UBaseType_t uxItemSize ); /* 每个消息大小,单位字节 */

函数描述: 函数 xQueueCreate 用于创建消息队列。

  • 第 1 个参数是消息队列支持的消息个数。
  • 第 2 个参数是每个消息的大小,单位字节。
  • 返回值,如果创建成功会返回消息队列的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不足,无法为此消息队列提供所需的空间会返回 NULL。

使用这个函数要注意以下问题:FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址,这点要特别注意。每一次传递都是uxItemSize 个字节。

使用举例:

static QueueHandle_t xQueue1 = NULL;
static QueueHandle_t xQueue2 = NULL;
/*
*********************************************************************************************************
* 函 数 名: AppObjCreate
* 功能说明: 创建任务通信机制
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void AppObjCreate (void)
{
  /* 创建 10 个 uint8_t 型消息队列 */
  xQueue1 = xQueueCreate(10, sizeof(uint8_t));
  if( xQueue1 == 0 )
  {
    /* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
  }
  /* 创建 10 个存储指针变量的消息队列,由于 CM3/CM4 内核是 32 位机,一个指针变量占用 4 个字节 */
  xQueue2 = xQueueCreate(10, sizeof(struct Msg *));
  if( xQueue2 == 0 )
  {
    /* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
  }
}

  1.2函 数 xQueueSend

函数原型:
    BaseType_t xQueueSend(
            QueueHandle_t xQueue, /* 消息队列句柄 */
            const void * pvItemToQueue, /* 要传递数据地址 */
            TickType_t xTicksToWait /* 等待消息队列有空间的最大等待时间 */
            );

函数描述:
    函数 xQueueSend 用于任务中消息发送。

  • 第 1 个参数是消息队列句柄。
  • 第 2 个参数要传递数据地址,每次发送都是将消息队列创建函数 xQueueCreate 所指定的单个消息大小复制到消息队列空间中。
  • 第 3 个参数是当消息队列已经满时,等待消息队列有空间时的最大等待时间,单位系统时钟节拍。
  • 返回值,如果消息成功发送返回 pdTRUE,否则返回 errQUEUE_FULL。

   

    使用这个函数要注意以下问题:

  • FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址。
  • 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是xQueueSendFromISR。
  • 如果消息队列已经满且第三个参数为 0,那么此函数会立即返回。
  • 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第三个参数配置为 portMAX_DELAY,那么此发送函数会永久等待直到消息队列有空间可以使用。
  • 消息队列还有两个函数 xQueueSendToBack 和 xQueueSendToFront,函数 xQueueSendToBack实现的是 FIFO 方式的存取,函数 xQueueSendToFront 实现的是 LIFO 方式的读写。我们这里说的函数 xQueueSend 等效于 xQueueSendToBack,即实现的是 FIFO 方式的存取。

使用举例:

static QueueHandle_t xQueue1 = NULL;
static QueueHandle_t xQueue2 = NULL;
typedef struct Msg
{
  uint8_t ucMessageID;
  uint16_t usData[2];
  uint32_t ulData[2];
}MSG_T; MSG_T g_tMsg; /* 定义一个结构体用于消息队列 */
/*
*********************************************************************************************************
* 函 数 名: vTaskTaskUserIF
* 功能说明: 接口消息处理。
* 形 参: pvParameters 是在创建该任务时传递的形参
* 返 回 值: 无
* 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反)
*********************************************************************************************************
*/
static void vTaskTaskUserIF(void *pvParameters)
{
MSG_T *ptMsg;
uint8_t ucCount = 0; /* 初始化结构体指针 */
ptMsg = &g_tMsg; /* 初始化数组 */
ptMsg->ucMessageID = 0;
ptMsg->ulData[0] = 0;
ptMsg->usData[0] = 0; while(1)
{ if(ucKeyCode == 1)
{ ucCount++; /* 向消息队列发数据,如果消息队列满了,等待10个时钟节拍 */
if( xQueueSend(xQueue1,
(void *) &ucCount,
(TickType_t)10) != pdPASS )
{
/* 发送失败,即使等待了10个时钟节拍 */
printf("K1键按下,向xQueue1发送数据失败,即使等待了10个时钟节拍\r\n");
}
else
{
/* 发送成功 */
printf("K1键按下,向xQueue1发送数据成功\r\n");
} ucKeyCode = 0;
} /* K2键按下 启动单次定时器中断,50ms后在定时器中断将任务vTaskLED恢复 */
if(ucKeyCode == 2)
{
ptMsg->ucMessageID++;
ptMsg->ulData[0]++;;
ptMsg->usData[0]++; /* 使用消息队列实现指针变量的传递 */
if(xQueueSend(xQueue2, /* 消息队列句柄 */
(void *) &ptMsg, /* 发送结构体指针变量ptMsg的地址 */
(TickType_t)10) != pdPASS )
{
/* 发送失败,即使等待了10个时钟节拍 */
printf("K2键按下,向xQueue2发送数据失败,即使等待了10个时钟节拍\r\n");
}
else
{
/* 发送成功 */
printf("K2键按下,向xQueue2发送数据成功\r\n");
} ucKeyCode = 0;
} vTaskDelay(20);
} }

1.3函 数 xQueueSendFromISR

函数原型:
    BaseType_t xQueueSendFromISR
               (
                QueueHandle_t xQueue, /* 消息队列句柄 */
                const void *pvItemToQueue, /* 要传递数据地址 */
                BaseType_t *pxHigherPriorityTaskWoken /* 高优先级任务是否被唤醒的状态保存 */
               );

函数描述:
    函数 xQueueSendFromISR 用于中断服务程序中消息发送。

  • 第 1 个参数是消息队列句柄。
  • 第 2 个参数要传递数据地址,每次发送都是将消息队列创建函数 xQueueCreate 所指定的单个消息大小复制到消息队列空间中。
  • 第3个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是pdTRUE,说明有高优先级任务要执行,否则没有。
  • 返回值,如果消息成功发送返回 pdTRUE,否则返回 errQUEUE_FULL。

使用这个函数要注意以下问题:

  • FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址。正因为这个原因,用户在创建消息队列时单个消息大小不可太大,因为一定程度上面会增加中断服务程序的执行时间。
  • 此函数是用于中断服务程序中调用的,故不可以在任务代码中调用此函数,任务代码中使用的是xQueueSend。
  • 消息队列还有两个函数 xQueueSendToBackFromISR 和 xQueueSendToFrontFromISR,函数xQueueSendToBackFromISR 实现的是 FIFO 方式的存取,函数 xQueueSendToFrontFromISR 实现的是 LIFO 方式的读写。我们这里说的函数 xQueueSendFromISR 等效于xQueueSendToBackFromISR,即实现的是 FIFO 方式的存取。

使用举例:

void  BASIC_TIMx_IRQHandler(void)
{
if(1 == temp)
{
TIM_ClearITPendingBit(BASIC_TIMx , TIM_IT_Update); TIM_ITConfig(BASIC_TIMx,TIM_IT_Update,DISABLE); BaseType_t xHigherPriorityTaskWoken = pdFALSE; g_uiCount++; /* 向消息队列发数据 */
xQueueSendFromISR(xQueue1,
(void *)&g_uiCount,
&xHigherPriorityTaskWoken); /* 如果xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }
if(2 == temp)
{
TIM_ClearITPendingBit(BASIC_TIMx , TIM_IT_Update); TIM_ITConfig(BASIC_TIMx,TIM_IT_Update,DISABLE); MSG_T *ptMsg;
BaseType_t xHigherPriorityTaskWoken = pdFALSE; /* 初始化结构体指针 */
ptMsg = &g_tMsg; /* 初始化数组 */
ptMsg->ucMessageID++;
ptMsg->ulData[0]++;
ptMsg->usData[0]++; /* 向消息队列发数据 */
xQueueSendFromISR(xQueue2,
(void *)&ptMsg,
&xHigherPriorityTaskWoken); /* 如果xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } }

1.4函 数 xQueueReceive

函数原型:
    BaseType_t xQueueReceive(
             QueueHandle_t xQueue, /* 消息队列句柄 */
             void *pvBuffer, /* 接收消息队列数据的缓冲地址 */
             TickType_t xTicksToWait /* 等待消息队列有数据的最大等待时间 */
             );

函数描述:
    函数 xQueueReceive 用于接收消息队列中的数据。

  • 第 1 个参数是消息队列句柄。
  • 第 2 个参数是从消息队列中复制出数据后所储存的缓冲地址,缓冲区空间要大于等于消息队列创建函数 xQueueCreate 所指定的单个消息大小,否则取出的数据无法全部存储到缓冲区,从而造成内存溢出。
  • 第 3 个参数是消息队列为空时,等待消息队列有数据的最大等待时间,单位系统时钟节拍。
  • 返回值,如果接收到消息返回 pdTRUE,否则返回 pdFALSE。

使用这个函数要注意以下问题:

  • 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序使用的是xQueueReceiveFromISR。
  • 如果消息队列为空且第三个参数为 0,那么此函数会立即返回。
  • 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第三个参数配置为 portMAX_DELAY,那么此函数会永久等待直到消息队列有数据。

使用举例:

/*
*********************************************************************************************************
* 函 数 名: vTaskMsgPro
* 功能说明: 消息处理,使用函xQueueReceive接收任务vTaskTaskUserIF消息队列中的数据。
* 形 参: pvParameters 是在创建该任务时传递的形参
* 返 回 值: 无
* 优 先 级: 3
*********************************************************************************************************
*/
static void vTaskMsgPro(void *pvParameters)
{
BaseType_t xResult;
const TickType_t xMaxBlockTime = pdMS_TO_TICKS(300); /* 设置最大等待时间为300ms */
uint8_t ucQueueMsgValue; while(1)
{
xResult = xQueueReceive(xQueue1, /* 消息队列句柄 */
(void *)&ucQueueMsgValue, /* 存储接收到的数据到变量ucQueueMsgValue中 */
(TickType_t)xMaxBlockTime);/* 设置阻塞时间 */ if(xResult == pdPASS)
{
/* 成功接收,并通过串口将数据打印出来 */
printf("接收到消息队列数据ucQueueMsgValue = %d\r\n", ucQueueMsgValue);
}
else
{
BEEP_TOGGLE;
}
}
}
void vTaskLed1(void *pvParameters)
{ MSG_T *ptMsg;
BaseType_t xResult;
const TickType_t xMaxBlockTime = pdMS_TO_TICKS(200); /* 设置最大等待时间为200ms */ while(1)
{
xResult = xQueueReceive(xQueue2, /* 消息队列句柄 */
(void *)&ptMsg, /* 这里获取的是结构体的地址 */
(TickType_t)xMaxBlockTime);/* 设置阻塞时间 */ if(xResult == pdPASS)
{
/* 成功接收,并通过串口将数据打印出来 */
printf("接收到消息队列数据ptMsg->ucMessageID = %d\r\n", ptMsg->ucMessageID);
printf("接收到消息队列数据ptMsg->ulData[0] = %d\r\n", ptMsg->ulData[0]);
printf("接收到消息队列数据ptMsg->usData[0] = %d\r\n", ptMsg->usData[0]);
}
else
{
/* 超时 */
LED1_TOGGLE;
} }
}

实验通过AppObjCreate函数创建两个队列消息,容量都是10个消息,队列1,2分别为uint8_t和struct Msg *类型,按键K1,实现队列1一个计数的增加,然后在Beep任务中接收这个变化的值,任务2实现结构体元素的增加,在LED任务中接收这个增量并打印出来。需要说明的是,freertos消息队列是通过副本机制传递的,而不是引用。

freertos通过使用memcpy复制的内容。以简单的数据元素为例:

uint8_t ucCount = 0;

xQueueSend(xQueue1,(void *) &ucCount,(TickType_t)10)

这里是发送队列消息函数,下面看接收:

uint8_t ucQueueMsgValue;

xQueueReceive(xQueue1, /* 消息队列句柄 */
(void *)&ucQueueMsgValue, /* 存储接收到的数据到变量ucQueueMsgValue中 */
(TickType_t)xMaxBlockTime)

这里是最简单的uint_8类型元素,要想把发送函数的uint_8定义的数据,包括该数据在发送函数之前被更改后的值发送给接收函数,我们需要传递给发送函数send一个uint_8定义数据的地址,这样可以通过地址传递到memcpy函数,实现复制,这也就是为什么上面说的freertos的消息队列不是引用而是复制,要是引用的话,可以直接传这个uint_8类型的数据,而我们此时在freertos操作系统上,是副本传递,通过memcpy,所以需要给uint_8类型数据的地址。

这个或许并不具有什么迷惑性,但是,官方的参考demo,要是不认真理解一下,是想不通的。

在本次实验中传递一个结构体就是官方的参考历程:

发送函数:

MSG_T *ptMsg;//MSG是个结构体

ptMsg = &g_tMsg;//g_tMsg是一个结构实体而且是全局区定义的

/* 初始化数组 */
ptMsg->ucMessageID = 0;
ptMsg->ulData[0] = 0;
ptMsg->usData[0] = 0; xQueueSend(xQueue2, /* 消息队列句柄 */
(void *) &ptMsg, /* 发送结构体指针变量ptMsg的地址 */
(TickType_t)10) 接收函数: MSG_T *ptMsg; xQueueReceive(xQueue2, /* 消息队列句柄 */
(void *)&ptMsg, /* 这里获取的是结构体的地址 */
(TickType_t)xMaxBlockTime);/* 设置阻塞时间 */

这里的关键就在第二个参数ptMsg,它已经是指针了,为什么还要取地址,这样不是一个二级指针了吗,而它的参数是void *,给人的感觉应该就是传一个地址,虽然二级指针也是地址,但是总觉得不应该设计成二级指针赋值给一个一级指针,哪怕你是void*。但是我们既然使用了freertos,就要遵循别人的设计,别人这样做,肯定有自己的道理,我们做到熟练应用即可。

试想,消息发送函数,要发送数据,要得到这个数据的地址以给memcopy,如果传递的数据本身就是地址(指针),那么我们要把这个地址传到接收函数去,就应该得到此时指针的地址才行,也就是传递一个指针的值,注意不是指针指向的值。关键我们要通过memcpy函数,传递一个指针的值通过memcpy必然是需要二级指针的,这样才可以操作一级指针的值,这样也就可以理解为什么ptMsg已经是指针了,却还是要传递ptMsg的地址,因为只有这样,才可以通过memcpy函数把ptMsg指针的值给到接收函数的指针,这样在接收函数中操作这个结构体类型的指针,就可以得到发送端的数据。这样做的好处是,避免了大数据的拷贝,只拷贝指针,提高了效率,但是使用指针,一定不要在栈空间开辟,这也是为什么我们定义g_tMsg结构体实体在全局区。但是freertos任务中一直有while(1),元素生命周期一直都在,此时还是可以使用局部变量做数据传递工具,但是这样的编程模式应该摒弃,我们采用全局区开辟的空间。

那么你可能会问了,那我直接给指针ptMsg看看行不行呢,不给指针的地址即&ptMsg。答案是肯定的,不行。给ptMsg,相当于把ptMsg指向的数据给了接收端,而freertos要求是的,你传一个你想要发送消息的地址,我们想要发送的消息是ptMsg,它的地址是&ptMsg,所以我们必须传递&ptMsg。并不能简单看类型是否完全贴切,要看源码内部实现,毕竟强制类型转换太霸道。

再者,你还是觉得这样很诧异,那么你可以使用结构,而不要使用结构体指针,这样你传递的时候就是这个结构的指针了。但是注意,使用结构本身不使用结构体指针的时候,创建消息队列里面的siezof要改成结构体而不再是上面的结构体指针:

xQueue2 = xQueueCreate(10, sizeof(struct Msg ));

当然后面的->操作,要改成 . 操作。

原文:地址未知

FreeRTOS消息队列的更多相关文章

  1. FreeRTOS 消息队列

    以下基础内容转载自安富莱电子: http://forum.armfly.com/forum.php 本章节为大家讲解 FreeRTOS 的一个重要的通信机制----消息队列,初学者要熟练掌握,因为消息 ...

  2. 继续学习freertos消息队列

    写在前面:杰杰这个月很忙~所以并没有时间更新,现在健身房闭馆装修,晚上有空就更新一下!其实在公众号没更新的这段日子,每天都有兄弟在来关注我的公众号,这让我受宠若惊,在这里谢谢大家的支持啦!!谢谢^ 在 ...

  3. 【FreeRTOS学习04】小白都能懂的 Queue Management 消息队列使用详解

    消息队列作为任务间同步扮演着必不可少的角色: 相关文章 [FreeRTOS实战汇总]小白博主的RTOS学习实战快速进阶之路(持续更新) 文章目录 相关文章 1 前言 2 xQUEUE 3 相关概念 3 ...

  4. ThreadX——IPC应用之消息队列

    作者:zzssdd2 E-mail:zzssdd2@foxmail.com 一.应用简介 消息队列是RTOS中常用的一种数据通信方式,常用于任务与任务之间或是中断与任务之间的数据传递.在裸机系统中我们 ...

  5. 消息队列——RabbitMQ学习笔记

    消息队列--RabbitMQ学习笔记 1. 写在前面 昨天简单学习了一个消息队列项目--RabbitMQ,今天趁热打铁,将学到的东西记录下来. 学习的资料主要是官网给出的6个基本的消息发送/接收模型, ...

  6. 消息队列 Kafka 的基本知识及 .NET Core 客户端

    前言 最新项目中要用到消息队列来做消息的传输,之所以选着 Kafka 是因为要配合其他 java 项目中,所以就对 Kafka 了解了一下,也算是做个笔记吧. 本篇不谈论 Kafka 和其他的一些消息 ...

  7. .net 分布式架构之业务消息队列

    开源QQ群: .net 开源基础服务  238543768 开源地址: http://git.oschina.net/chejiangyi/Dyd.BusinessMQ ## 业务消息队列 ##业务消 ...

  8. 【原创经验分享】WCF之消息队列

    最近都在鼓捣这个WCF,因为看到说WCF比WebService功能要强大许多,另外也看了一些公司的招聘信息,貌似一些中.高级的程序员招聘,都有提及到WCF这一块,所以,自己也关心关心一下,虽然目前工作 ...

  9. Java消息队列--ActiveMq 实战

    1.下载安装ActiveMQ ActiveMQ官网下载地址:http://activemq.apache.org/download.html ActiveMQ 提供了Windows 和Linux.Un ...

随机推荐

  1. 微信小程序setdata修改数组或对象

    1.this.setdata修改数组的固定一项的值 changeItemInArr: function() { this.setData({ 'arr[0].text':'changed data' ...

  2. Spring的三种注入

    在学习Spring的过程中,其中一个很重要的就是依赖注入DI,在此总结一下 注入方式有三种: 一.构造器注入 二.Set方式注入(重点) 三.扩展方式注入 构造器注入: a.默认使用无参构造函数创建对 ...

  3. 获取两个时间点间的随机时间&时间戳

    获取两个时间点间的随机时间&时间戳 方案一 # python2 不兼容,python3正常 import datetime,random def randomtimes(start, end, ...

  4. TensorFlow损失函数

    TensorFlow损失函数 正如前面所讨论的,在回归中定义了损失函数或目标函数,其目的是找到使损失最小化的系数.本文将介绍如何在 TensorFlow 中定义损失函数,并根据问题选择合适的损失函数. ...

  5. nvGRAPH三角形计数和遍历示例

    nvGRAPH三角形计数和遍历示例 #include " stdlib.h" #include" inttypes.h" #include" stdi ...

  6. TensorRT 3:更快的TensorFlow推理和Volta支持

    TensorRT 3:更快的TensorFlow推理和Volta支持 TensorRT 3: Faster TensorFlow Inference and Volta Support 英伟达Tens ...

  7. 开放式神经网络交换-ONNX(上)

    目的 本文档包含ONNX语义的规范性规范. "onnx"文件夹下的.proto和.proto3文件构成了用协议缓冲区定义语言编写的语法规范..proto和.proto3文件中的注释 ...

  8. ES6中的变量结构赋值

    小编的上一篇文章更新了es6中关于变量定义的问题,这篇文章继续来一些实用的干货,关于数组.对象的赋值问题.特别是在前后端合作项目的时候,对后端数据的拆分,还有就是对于函数的默认值的惰性赋值问题.看完下 ...

  9. HashMap底层实现原理及面试常见问题

    HashMap底层源码分析 1.HashMap底层采用的存储结构 1.在JDK1.7及之前采用的存储结构是数组+链表 2.到了JDK1.8之后采用的是数组+链表+红黑树 2.HashMap实现的原理 ...

  10. maven 安装、下载、配置,idea中的maven设置

    1.从Maven官网下载压缩包 2.将压缩包解压到你像放置Maven的路径,我放置在D:\0_FileSave\Maven 3.创建本地仓库  MavenRepository 在Maven解压路径下D ...