概述
最近有在校的學生朋友在問我,資料結構中的佇列在實際的軟體開發專案中有什麼樣的用處。

大家都知道,佇列的特點是先入先出,即資料是按照入佇列的順序出佇列的。在實際的軟體開發專案中,當一箇中間模組需要接收和傳送大量的訊息時,佇列就可以大展身手了。我們可以將接收到的資料儲存在一個全域性佇列中,然後在另外的程式流程中將資料從同一個全域性佇列中取出來,經過一定的處理之後將訊息傳送到另外的模組。這樣做可以降低程式的效能瓶頸。

本文用實際的C程式碼示例了簡單的資料入佇列和出佇列的方法,大家可據此瞭解佇列的實際用法,也可參照來實現更加複雜的佇列操作。

C程式碼

/**********************************************************************
* 版權所有 (C)2016, Zhou Zhaoxiong
*
* 檔名稱:QueueUse.c
* 檔案標識:無
* 內容摘要:示例佇列的使用(入隊和出隊)
* 其它說明:無
* 當前版本:V1.0
* 作    者:Zhou Zhaoxiong
* 完成日期:20160811
*
**********************************************************************/
#include <stdio.h>
#include <string.h>
#include <ftw.h>
#include <pthread.h>
#include <time.h>


// 重定義資料型別
typedef signed   int        INT32;
typedef unsigned int        UINT32;
typedef unsigned char       UINT8;

// 巨集定義
#define     MAX_QUEUE      10000          // 最大佇列元素個數

// 結構體變數
typedef struct
{
    UINT32 iID;             // 編號
    UINT8  szInfo[100];     // 描述
} T_StructInfo;

// 全域性變數定義
T_StructInfo g_tQueue[MAX_QUEUE] = {0};      // 佇列結構體
UINT32 g_iQueueHead = 0;                     // 佇列頭部索引
UINT32 g_iQueueTail = 0;                     // 佇列尾部索引
pthread_mutex_t     g_mutex_queue_cs;        // 互斥訊號量
pthread_cond_t      queue_cv;
pthread_mutexattr_t g_MutexAttr;

// 函式宣告
void PutDataIntoQueue(void);
void GetDataFromQueue(void);
INT32 EnQueue(T_StructInfo tQueueData);
INT32 DeQueue(T_StructInfo *ptStructData);
void Sleep(UINT32 iCountMs);


/****************************************************************
* 功能描述: 主函式
* 輸入引數: 無
* 輸出引數: 無
* 返 回 值: 0-執行完成
* 其他說明: 無
* 修改日期       版本號        修改人        修改內容
* -------------------------------------------------------------
* 20160811        V1.0     Zhou Zhaoxiong     建立
****************************************************************/
INT32 main(void)
{
    pthread_mutex_init(&g_mutex_queue_cs, &g_MutexAttr);
    pthread_cond_init(&queue_cv, NULL);


    // 在迴圈中執行入隊和出隊操作
    while (1)
    {
        PutDataIntoQueue();  // 資料入隊


        Sleep(5 * 1000);     // 間隔5秒


        GetDataFromQueue();  // 資料出隊


        Sleep(60 * 1000);    // 每一分鐘執行一次出隊和入隊
    }


    return 0;
}




/****************************************************************
* 功能描述: 將資料加入佇列中
* 輸入引數: 無
* 輸出引數: 無
* 返 回 值: 0-成功   -1-失敗
* 其他說明: 無
* 修改日期       版本號        修改人        修改內容
* -------------------------------------------------------------
* 20160811        V1.0     Zhou Zhaoxiong     建立
****************************************************************/
void PutDataIntoQueue(void)
{
    T_StructInfo tQueueData = {0};
    static UINT32 iCountNum = 0;


    // 對結構體的變數進行賦值
    tQueueData.iID = iCountNum;
    snprintf(tQueueData.szInfo, sizeof(tQueueData.szInfo) - 1, "zhou%d", iCountNum);


    // 計數值累加
    iCountNum ++;
    if (iCountNum >= MAX_QUEUE-1)
    {
        iCountNum = 0;
    }


    // 將資料加入佇列(一直等到加入成功之後才退出)
    while (EnQueue(tQueueData) == -1)
    {
        Sleep(1000);       // 加入失敗,1秒後重試
    }


    // 列印加入的資料
    printf("PutDataIntoQueue: ID=%d, Info=%s\n", tQueueData.iID, tQueueData.szInfo);
}




/****************************************************************
* 功能描述: 將資料取出佇列中
* 輸入引數: 無
* 輸出引數: 無
* 返 回 值: 0-成功   -1-失敗
* 其他說明: 無
* 修改日期       版本號        修改人        修改內容
* -------------------------------------------------------------
* 20160811        V1.0     Zhou Zhaoxiong     建立
****************************************************************/
void GetDataFromQueue(void)
{
    T_StructInfo tQueueData = {0};


    if (DeQueue(&tQueueData) == -1)
    {
        return;
    }


    // 列印取出的資料
    printf("GetDataFromQueue: ID=%d, Info=%s\n", tQueueData.iID, tQueueData.szInfo);
}




/****************************************************************
* 功能描述: 資料入佇列
* 輸入引數: tQueueData-佇列資料
* 輸出引數: 無
* 返 回 值: 0-成功   -1-失敗
* 其他說明: 無
* 修改日期       版本號        修改人        修改內容
* -------------------------------------------------------------
* 20160811        V1.0     Zhou Zhaoxiong     建立
****************************************************************/
INT32 EnQueue(T_StructInfo tQueueData)
{
    INT32  iRetVal  = 0;
    UINT32 iNextPos = 0;


    pthread_mutex_lock(&g_mutex_queue_cs);
    iNextPos = g_iQueueTail + 1;


    if (iNextPos >= MAX_QUEUE)
    {
        iNextPos = 0;
    }


    if (iNextPos == g_iQueueHead)
    {
        iRetVal = -1;   // 已達到佇列的最大長度
    }
    else
    {
        // 入佇列
        memset(&g_tQueue[g_iQueueTail], 0x00,  sizeof(T_StructInfo));
        memcpy(&g_tQueue[g_iQueueTail], &tQueueData, sizeof(T_StructInfo));


        g_iQueueTail = iNextPos;
    }


    pthread_cond_signal(&queue_cv);
    pthread_mutex_unlock(&g_mutex_queue_cs);


    return iRetVal;
}




/****************************************************************
* 功能描述: 資料出佇列
* 輸入引數: ptStructData-佇列資料
* 輸出引數: 無
* 返 回 值: 0-成功   -1-失敗
* 其他說明: 無
* 修改日期       版本號        修改人        修改內容
* -------------------------------------------------------------
* 20160811        V1.0     Zhou Zhaoxiong     建立
****************************************************************/
INT32 DeQueue(T_StructInfo *ptStructData)
{
    T_StructInfo tQueueData = {0};


    if (ptStructData == NULL)
    {
        return -1;
    }


    pthread_mutex_lock(&g_mutex_queue_cs);


    while (g_iQueueHead == g_iQueueTail)
    {
        pthread_cond_wait(&queue_cv, &g_mutex_queue_cs);
    }


    memset(&tQueueData, 0x00, sizeof(T_StructInfo));
    memcpy(&tQueueData, &g_tQueue[g_iQueueHead], sizeof(T_StructInfo));
    g_iQueueHead ++;


    if (g_iQueueHead >= MAX_QUEUE)
    {
        g_iQueueHead = 0;
    }


    pthread_mutex_unlock(&g_mutex_queue_cs);
    memcpy(ptStructData, &tQueueData, sizeof(T_StructInfo));


    return 0;
}




/**********************************************************************
* 功能描述: 程式休眠
* 輸入引數: iCountMs-休眠時間(單位:ms)
* 輸出引數: 無
* 返 回 值: 無
* 其它說明: 無
* 修改日期      版本號       修改人        修改內容
* ------------------------------------------------------------------
* 20160811       V1.0     Zhou Zhaoxiong     建立
********************************************************************/ 
void Sleep(UINT32 iCountMs)
{
    struct timeval t_timeout = {0};


    if (iCountMs < 1000)
    {
        t_timeout.tv_sec  = 0;
        t_timeout.tv_usec = iCountMs * 1000;
    }
    else
    {
        t_timeout.tv_sec  = iCountMs / 1000;
        t_timeout.tv_usec = (iCountMs % 1000) * 1000;
    }
    select(0, NULL, NULL, NULL, &t_timeout);    // 呼叫select函式阻塞程式
}

程式執行情況
我們將上面編寫好的QueueUse.c檔案上傳到Linux機器上,使用“gcc -g -o QueueUseQueueUse.c”命令編譯之後,生成QueueUse檔案。之後,執行“QueueUse”命令,即可看到程式的執行結果(結果會不斷地更新)如下:

~/zhouzx/Test/QueueUse> QueueUse
PutDataIntoQueue: ID=0, Info=zhou0
GetDataFromQueue: ID=0, Info=zhou0
PutDataIntoQueue: ID=1, Info=zhou1
GetDataFromQueue: ID=1, Info=zhou1
PutDataIntoQueue: ID=2, Info=zhou2
GetDataFromQueue: ID=2, Info=zhou2
PutDataIntoQueue: ID=3, Info=zhou3
GetDataFromQueue: ID=3, Info=zhou3

我們看到,資料先是被加入到佇列中,然後再從佇列中取出來。

程式說明
第一,在本程式中,入佇列和出佇列是在同一個函式中完成的,但是,在實際開發專案的程式中,入佇列和出佇列一般是在不同的程式流程(兩個不同的執行緒)中完成的。

第二,本程式的資料入佇列操作是在EnQueue函式中完成的,資料出佇列操作是在DeQueue函式中完成的,全域性變數g_tQueue用於存放需要處理的資料。

第三,在實際開發專案的程式中,有可能會有很多流程都會呼叫入佇列和出佇列的函式,為了防止多個流程同時向佇列中加入資料或取出資料,在EnQueue和DeQueue函式中使用了鎖操作。也就是說,在操作資料之前,先用pthread_mutex_lock函式執行加鎖操作,在處理完資料之後,再用pthread_mutex_unlock函式執行解鎖操作。

第四,在實際開發專案中,為了防止程式從佇列中取資料的速率過快而使得下游模組處理不過來,我們常在從佇列取出資料之後發訊息的流程中控制資料的傳送速率,具體每秒鐘傳送多少條可在配置檔案中設定。