FreeRTOS 的一個重要的通信機制----消息隊列,消息隊列在實際項目中應用較多。
1、消息隊列
1.1 消息隊列的概念及其作用
消息隊列就是通過 RTOS 核心提供的服務,任務或中斷服務子程式可以将一個消息(注意,FreeRTOS消息隊列傳遞的是實際資料,并不是資料位址,RTX,uCOS-II 和 uCOS-III 是傳遞的位址)放入到隊列。同樣,一個或者多個任務可以通過 RTOS 核心服務從隊列中得到消息。通常,先進入消息隊列的消息先傳給任務,也就是說,任務先得到的是最先進入到消息隊列的消息,即先進先出的原則(FIFO),FreeRTOS的消息隊列支援 FIFO 和 LIFO 兩種資料存取方式。
也許有不了解的初學者會問采用消息隊列多麻煩,搞個全局數組不是更簡單,其實不然。在裸機程式設計時,使用全局數組的确比較友善,但是在加上 RTOS 後就是另一種情況了。相比消息隊列,使用全局數組主要有如下四個問題:
使用消息隊列可以讓 RTOS 核心有效地管理任務,而全局數組是無法做到的,任務的逾時等機制需要使用者自己去實作。
使用了全局數組就要防止多任務的通路沖突,而使用消息隊列則處理好了這個問題,使用者無需擔心。
使用消息隊列可以有效地解決中斷服務程式與任務之間消息傳遞的問題。
FIFO 機制更有利于資料的處理。
1.2 FreeRTOS 任務間消息隊列的實作
任務間消息隊列的實作是指各個任務之間使用消息隊列實作任務間的通信。下面我們通過如下的框圖來說明一下 FreeRTOS 消息隊列的實作,讓大家有一個形象的認識。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISPrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdsATOfd3bkFGazxCMx8VesATMfhHLlN3XnxCMwEzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SN5ITMyYDMhFDMiJGMwEDOxYzXzITMxcTMyAzLcFTMxIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL3M3Lc9CX6MHc0RHaiojIsJye.png)
運作條件:
建立消息隊列,可以存放 10 個消息。
建立 2 個任務 Task1 和 Task2,任務 Task1 向消息隊列放資料,任務 Task2 從消息隊列取資料。
FreeRTOS 的消息存取采用 FIFO 方式。
運作過程主要有以下兩種情況:
任務 Task1 向消息隊列放資料,任務 Task2 從消息隊列取資料,如果放資料的速度快于取資料的速度,那麼會出現消息隊列存放滿的情況,FreeRTOS 的消息存放函數 xQueueSend 支援逾時等待,使用者可以設定逾時等待,直到有空間可以存放消息或者設定的逾時時間溢出。
任務 Task1 向消息隊列放資料,任務 Task2 從消息隊列取資料,如果放資料的速度慢于取資料的速度,那麼會出現消息隊列為空的情況,FreeRTOS 的消息擷取函數 xQueueReceive 支援逾時等待,使用者可以設定逾時等待,直到消息隊列中有消息或者設定的逾時時間溢出。
上面就是一個簡單的 FreeRTOS 任務間消息隊列通信過程,FIFO 方式資料存取過程的動态示範看官方位址:點選這裡裡面的 GIF 圖檔。
1.3 FreeRTOS 中斷方式消息隊列的實作
FreeRTOS 中斷方式消息隊列的實作是指中斷函數和 FreeRTOS 任務之間使用消息隊列。下面我們通過如下的框圖來說明一下 FreeRTOS 消息隊列的實作,讓大家有一個形象的認識。
建立 1 個任務 Task1 和一個序列槽接收中斷。
FreeRTOS 的消息存取采用 FIFO 方式。
中斷服務程式向消息隊列放資料,任務 Task1 從消息隊列取資料,如果放資料的速度快于取資料的速度,那麼會出現消息隊列存放滿的情況。由于中斷服務程式裡面的消息隊列發送函數xQueueSendFromISR 不支援逾時設定,是以發送前要通過函數 xQueueIsQueueFullFromISR 檢測消息隊列是否滿。
中斷服務程式向消息隊列放資料,任務 Task1 從消息隊列取資料,如果放資料的速度慢于取資料的速度,那麼會出現消息隊列存為空的情況。在 FreeRTOS 的任務中可以通過函數 xQueueReceive 擷取消息,因為此函數可以設定逾時等待,直到消息隊列中有消息存放或者設定的逾時時間溢出。
上面就是一個簡單的 FreeRTOS 中斷方式消息隊列通信過程。實際應用中,中斷方式的消息機制要注意以下四個問題:
中斷函數的執行時間越短越好,防止其它低于這個中斷優先級的異常不能得到及時響應。
實際應用中,建議不要在中斷中實作消息處理,使用者可以在中斷服務程式裡面發送消息通知任務,在任務中實作消息處理,這樣可以有效地保證中斷服務程式的實時響應。同時此任務也需要設定為高優先級,以便退出中斷函數後任務可以得到及時執行。
中斷服務程式中一定要調用專用于中斷的消息隊列函數,即以 FromISR 結尾的函數。
在作業系統中實作中斷服務程式與裸機程式設計的差別。
如果 FreeRTOS 工程的中斷函數中沒有調用 FreeRTOS 的消息隊列 API 函數,與裸機程式設計是一樣的。
如果 FreeRTOS 工程的中斷函數中調用了 FreeRTOS 的消息隊列的 API 函數,退出的時候要檢測是否有高優先級任務就緒,如果有就緒的,需要在退出中斷後進行任務切換,這點與裸機程式設計稍有差別,詳見 實驗例程說明(中斷方式):
另外強烈推薦使用者将 Cortex-M3 核心的 STM32F103 和 Cortex-M4 核心的 STM32F407,F429的 NVIC 優先級分組設定為 4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);這樣中斷優先級的管理将非常友善。
使用者要在 FreeRTOS 多任務開啟前就設定好優先級分組,一旦設定好切記不可再修改。
2、消息隊列API函數
使用如下 23 個函數可以實作 FreeRTOS 的消息隊列:
xQueueCreateStatic()
vQueueDelete()
xQueueSend()
xQueueSendFromISR()
xQueueSendToBack()
xQueueSendToBackFromISR()
xQueueSendToFront()
xQueueSendToFrontFromISR()
xQueueReceive()
xQueueReceiveFromISR()
uxQueueMessagesWaiting()
uxQueueMessagesWaitingFromISR()
uxQueueSpacesAvailable()
xQueueReset()
xQueueOverwrite()
xQueueOverwriteFromISR()
xQueuePeek()
xQueuePeekFromISR()
vQueueAddToRegistry()
vQueueUnregisterQueue()
pcQueueGetName()
xQueueIsQueueFullFromISR()
xQueueIsQueueEmptyFromISR()
關于這 23 個函數的講解及其使用方法可以看 FreeRTOS 線上版手冊:
這裡重點的說以下 4 個函數:
xQueueCreate ()
xQueueSend ()
xQueueSendFromISR ()
xQueueReceive ()
2.1 函 數 xQueueCreate
函數原型:
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, /* 消息個數 */
UBaseType_t uxItemSize ); /* 每個消息大小,機關位元組 */
函數描述:
函數 xQueueCreate 用于建立消息隊列。
第 1 個參數是消息隊列支援的消息個數。
第 2 個參數是每個消息的大小,機關位元組。
傳回值,如果建立成功會傳回消息隊列的句柄,如果由于 FreeRTOSConfig.h 檔案中 heap 大小不足,
無法為此消息隊列提供所需的空間會傳回 NULL。
使用這個函數要注意以下問題:
1. 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 )
{
/* 沒有建立成功,使用者可以在這裡加入建立失敗的處理機制 */
}
}
2.2 函 數 xQueueSend
BaseType_t xQueueSend(
QueueHandle_t xQueue, /* 消息隊列句柄 */
const void * pvItemToQueue, /* 要傳遞資料位址 */
TickType_t xTicksToWait /* 等待消息隊列有空間的最大等待時間 */
);
函數 xQueueSend 用于任務中消息發送。
第 1 個參數是消息隊列句柄。
第 2 個參數要傳遞資料位址,每次發送都是将消息隊列建立函數 xQueueCreate 所指定的單個消息大小複制到消息隊列空間中。
第 3 個參數是當消息隊列已經滿時,等待消息隊列有空間時的最大等待時間,機關系統時鐘節拍。
傳回值,如果消息成功發送傳回 pdTRUE,否則傳回 errQUEUE_FULL。
1. FreeRTOS 的消息傳遞是資料的複制,而不是傳遞的資料位址。
2. 此函數是用于任務代碼中調用的,故不可以在中斷服務程式中調用此函數,中斷服務程式中使用的是xQueueSendFromISR。
3. 如果消息隊列已經滿且第三個參數為 0,那麼此函數會立即傳回。
4. 如果使用者将 FreeRTOSConfig.h 檔案中的宏定義 INCLUDE_vTaskSuspend 配置為 1 且第三個參數配置為 portMAX_DELAY,那麼此發送函數會永久等待直到消息隊列有空間可以使用。
5. 消息隊列還有兩個函數 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);
}
}
2.3 函 數 xQueueSendFromISR
BaseType_t xQueueSendFromISR
(
QueueHandle_t xQueue, /* 消息隊列句柄 */
const void *pvItemToQueue, /* 要傳遞資料位址 */
BaseType_t *pxHigherPriorityTaskWoken /* 高優先級任務是否被喚醒的狀态儲存 */
);
函數 xQueueSendFromISR 用于中斷服務程式中消息發送。
第 2 個參數要傳遞資料位址,每次發送都是将消息隊列建立函數 xQueueCreate 所指定的單個消息大小複制到消息隊列空間中。
第3個參數用于儲存是否有高優先級任務準備就緒。如果函數執行完畢後,此參數的數值是pdTRUE,說明有高優先級任務要執行,否則沒有。
傳回值,如果消息成功發送傳回 pdTRUE,否則傳回 errQUEUE_FULL。
1. FreeRTOS 的消息傳遞是資料的複制,而不是傳遞的資料位址。正因為這個原因,使用者在建立消息隊列時單個消息大小不可太大,因為一定程度上面會增加中斷服務程式的執行時間。
2. 此函數是用于中斷服務程式中調用的,故不可以在任務代碼中調用此函數,任務代碼中使用的是xQueueSend。
3. 消息隊列還有兩個函數 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);
}
}
2.4 函 數 xQueueReceive
BaseType_t xQueueReceive(
QueueHandle_t xQueue, /* 消息隊列句柄 */
void *pvBuffer, /* 接收消息隊列資料的緩沖位址 */
TickType_t xTicksToWait /* 等待消息隊列有資料的最大等待時間 */
);
函數 xQueueReceive 用于接收消息隊列中的資料。
第 2 個參數是從消息隊列中複制出資料後所儲存的緩沖位址,緩沖區空間要大于等于消息隊列建立函數 xQueueCreate 所指定的單個消息大小,否則取出的資料無法全部存儲到緩沖區,進而造成記憶體溢出。
第 3 個參數是消息隊列為空時,等待消息隊列有資料的最大等待時間,機關系統時鐘節拍。
傳回值,如果接收到消息傳回 pdTRUE,否則傳回 pdFALSE。
1. 此函數是用于任務代碼中調用的,故不可以在中斷服務程式中調用此函數,中斷服務程式使用的是xQueueReceiveFromISR。
2. 如果消息隊列為空且第三個參數為 0,那麼此函數會立即傳回。
3. 如果使用者将 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 ));
當然後面的->操作,要改成 . 操作。
實驗現象如下:
最後說兩句:
而我測試了,深度給1,但我發送兩個消息,程式還是可以工作,(并不是我給隊列深度為1,就隻能有一個隊列消息發送函數)這和發送接收的允許阻塞時間有關。
是以,在等待時間合适的情況下,深度隻給1,還是可以發送多次的。
Talk is cheap, show me the code