双向链表实现数据缓存

应用场景:当整个数据流对时序有要求时,后端(多个)消费者(数据处理)比前端数据生产者慢,就需要将前端生产的数据做缓存,如果缓存队列(深度可设置)满了就做丢数据处理


引用到Linux内核双向链表的使用


这里以YUV数据为例,下图中演示了2个通道的数据流
数据生产和数据存储为一个独立的线程;数据处理和数据释放为另一个独立线程,如果有多路数据通道,可以创建多组数据生产与处理对象

 

 双向链表头文件

#ifndef __APP_IPCAM_LL_H__
#define __APP_IPCAM_LL_H__
#include <stdbool.h>

#ifdef __cplusplus
extern "C"
{
#endif

#define APP_LL_CONTEXT_MAX      8
#define APP_DATA_COMSUMES_MAX   8

typedef int (*pfpDataSave)(void **dst, void *src);
typedef int (*pfpDataFree)(void **src);
typedef void (*pfpDataHandle)(void *data, void *param);
typedef int (*pfpDataConsumes)(void *pData, void *pCtx);

typedef struct llhead {
    struct llhead *prev;
    struct llhead *next;
} APP_LINK_LIST_S;

typedef struct APP_DATA_LL_T {
    APP_LINK_LIST_S link;
    void *pData;
} APP_DATA_LL_S;

typedef struct APP_DATA_PARAM_T {
    void *pParam;
    pfpDataSave fpDataSave;
    pfpDataFree fpDataFree;
    pfpDataHandle fpDataHandle;
    pfpDataConsumes fpDataConsumes[APP_DATA_COMSUMES_MAX];
} APP_DATA_PARAM_S;

typedef struct APP_DATA_CTX_T {
    APP_DATA_LL_S stHead;
    int LListDepth;
    bool bRunStatus;
    pthread_t pthread_id;
    pthread_mutex_t mutex;
    APP_DATA_PARAM_S stDataParam;
} APP_DATA_CTX_S;

int app_ipcam_LList_Data_Init(void * *pCtx, void *pParam);
int app_ipcam_LList_Data_DeInit(void * *pCtx);
int app_ipcam_LList_Data_Push(void *pData, void *pArgs);

#ifdef __cplusplus
}
#endif

#endif

双向链表实现实体

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <stddef.h>
#include "app_ipcam_ll.h"


#define LL_DATA_CACHE_DEPTH_MAX     5

#define LL_INIT(N) ((N)->next = (N)->prev = (N))

#define LL_HEAD(H) struct llhead H = { &H, &H }

#define LL_ENTRY(P,T,N) ((T *)((char *)(P) - offsetof(T, N)))

#define LL_ADD(H, N) do {       \
    ((H)->next)->prev = (N);    \
    (N)->next = ((H)->next);    \
    (N)->prev = (H);            \
    (H)->next = (N);            \
} while (0)

#define LL_TAIL(H, N) do {      \
    ((H)->prev)->next = (N);    \
    (N)->prev = ((H)->prev);    \
    (N)->next = (H);            \
    (H)->prev = (N);            \
} while (0)

#define LL_DEL(N) do {                  \
    ((N)->next)->prev = ((N)->prev);    \
    ((N)->prev)->next = ((N)->next);    \
    LL_INIT(N);                         \
} while (0)

#define LL_EMPTY(N) ((N)->next == (N))

#define LL_FOREACH(H,N) for (N = (H)->next; N != (H); N = (N)->next)

#define LL_FOREACH_SAFE(H,N,T)  for (N = (H)->next, T = (N)->next; N != (H); N = (T), T = (N)->next)

#define LL_DATA_NODE_DEL(pNode) do {                \
    if (pNode != NULL) {                            \
        ((pNode)->next)->prev = ((pNode)->prev);    \
        ((pNode)->prev)->next = ((pNode)->next);    \
    }                                               \
} while(0)

#define container_of(ptr, type, member) ({          \
    const typeof(((type *)0)->member) *mptr = ptr;  \
    (type *)((char *)mptr-offsetof(type, member));  \
})

#define LINK_LIST_DATA_POP_FRONT(PopPoint, Head, member) ({             \
    if(Head->next == Head) {                                            \
        PopPoint = NULL;                                                \
    } else {                                                            \
        PopPoint = container_of(Head->next, typeof(*PopPoint), member); \
        LL_DATA_NODE_DEL(Head->next);                                   \
    }                                                                   \
})

#define LINK_LIST_DATA_PUSH_TAIL(H, N) do { \
    ((H)->prev)->next = (N);                \
    (N)->prev = ((H)->prev);                \
    (N)->next = (H);                        \
    (H)->prev = (N);                        \
} while (0)

int app_ipcam_LList_Data_Pop(void **pData, void *pArgs)
{
    APP_DATA_CTX_S *pstDataCtx = (APP_DATA_CTX_S *)pArgs;

    APP_LINK_LIST_S *pHeadLink = &pstDataCtx->stHead.link;
    if (LL_EMPTY(pHeadLink)) {
        return -1;
    }

    APP_DATA_LL_S *pNodePop = NULL;

    pthread_mutex_lock(&pstDataCtx->mutex);
    LINK_LIST_DATA_POP_FRONT(pNodePop, pHeadLink, link);
    if(pNodePop) {
        pstDataCtx->LListDepth--;
    }
    pthread_mutex_unlock(&pstDataCtx->mutex);

    if(pNodePop) {
        *pData = pNodePop->pData;
        free(pNodePop);
    } else {
        return -1;
    }

    return 0;
}

int app_ipcam_LList_Data_Push(void *pData, void *pArgs)
{
    if (pData == NULL || pArgs == NULL) {
        printf("pData or pArgs is NULL!\n");
        return -1;
    }

    APP_DATA_CTX_S *pstDataCtx = (APP_DATA_CTX_S *)pArgs;
    APP_DATA_PARAM_S *pstDataParam = &pstDataCtx->stDataParam;
    APP_DATA_LL_S *pHead = &pstDataCtx->stHead;

    if (!pstDataCtx->bRunStatus) {
        printf("Link List Cache Not Running Now!!\n");
        return -1;
    }

    APP_DATA_LL_S *pNewNode = NULL;
    pNewNode = (APP_DATA_LL_S *)malloc(sizeof(APP_DATA_LL_S));
    if(pNewNode == NULL) {
        printf("pNewNode malloc failed!\n");
        return -1;
    }

    pNewNode->pData = NULL;

    if (pstDataParam->fpDataSave(&pNewNode->pData, pData) != 0) {
        free(pNewNode);
        printf("data save failded!\n");
        return -1;
    }

    if (pstDataCtx->LListDepth > LL_DATA_CACHE_DEPTH_MAX) {
        void *pDataDrop = NULL;
        printf("LL cache is full and drop data. (LList depth:%d > Max:%d) \n", pstDataCtx->LListDepth, LL_DATA_CACHE_DEPTH_MAX);
        if (app_ipcam_LList_Data_Pop(&pDataDrop, pArgs) != 0) {
            free(pNewNode);
            printf("LL data drop failded!\n");
            return -1;
        }
        if(pDataDrop) {
            if(pstDataParam->fpDataFree) {
                pstDataParam->fpDataFree(&pDataDrop);
            }
            pDataDrop = NULL;
        }
    }

    pthread_mutex_lock(&pstDataCtx->mutex);
    APP_LINK_LIST_S *pHeadLink = &pHead->link;
    APP_LINK_LIST_S *pNodeLink = &pNewNode->link;
    if (pHeadLink == NULL || pNodeLink == NULL) {
        printf(" pHeadLink or pNodeLink is NULL\n");
        pthread_mutex_unlock(&pstDataCtx->mutex);
        return -1;
    }

    LINK_LIST_DATA_PUSH_TAIL(pHeadLink, pNodeLink);

    pstDataCtx->LListDepth++;
    pthread_mutex_unlock(&pstDataCtx->mutex);

    return 0;

}

static void *Thread_LList_Data_Consume(void *pArgs)
{
    APP_DATA_CTX_S *pstDataCtx = (APP_DATA_CTX_S *)pArgs;
    APP_DATA_PARAM_S *pstDataParam = &pstDataCtx->stDataParam;
    
    void *pData = NULL;
    char TaskName[32] = {0};

    sprintf(TaskName, "DataConsume");
    prctl(PR_SET_NAME, TaskName, 0, 0, 0);
    while(pstDataCtx->bRunStatus) {
        if(app_ipcam_LList_Data_Pop(&pData, pArgs) == 0) {
            if(pData != NULL) {
                if(pstDataParam->fpDataHandle) {
                    pstDataParam->fpDataHandle(pData, pArgs);
                }
                if(pstDataParam->fpDataFree) {
                    pstDataParam->fpDataFree(&pData);
                }
                pData = NULL;
            }
        } else {
            usleep(5*1000);
        }
    }

    return NULL;
}

int app_ipcam_LList_Data_Init(void * *pCtx, void *pParam)
{
    if ((pCtx == NULL) || (pParam == NULL)) {
        printf("pCtx or pParam is NULL\n");
        return -1;
    }

    int s32Ret = 0;
    APP_DATA_PARAM_S *pDataParam = (APP_DATA_PARAM_S *)pParam;
    APP_DATA_CTX_S *pDataCtx = (APP_DATA_CTX_S *)malloc(sizeof(APP_DATA_CTX_S));
    if (pDataCtx == NULL) {
        printf("pDataCtx is NULL\n");
        return -1;
    }

    pDataCtx->stDataParam.pParam = (void *)pDataParam->pParam;
    pDataCtx->stDataParam.fpDataSave   = pDataParam->fpDataSave;
    pDataCtx->stDataParam.fpDataFree   = pDataParam->fpDataFree;
    pDataCtx->stDataParam.fpDataHandle = pDataParam->fpDataHandle;
    for (int i = 0; i < APP_DATA_COMSUMES_MAX; i++) {
        pDataCtx->stDataParam.fpDataConsumes[i] = pDataParam->fpDataConsumes[i];
    }

    pDataCtx->LListDepth = 0;

    pthread_mutex_init(&pDataCtx->mutex, NULL);

    /* init link list head */
    LL_INIT(&pDataCtx->stHead.link);

    pDataCtx->bRunStatus = true;
    s32Ret = pthread_create(&pDataCtx->pthread_id,
                            NULL,
                            Thread_LList_Data_Consume,
                            (void *)pDataCtx);
    if (s32Ret != 0) {
        pthread_mutex_destroy(&pDataCtx->mutex);
        goto EXIT;
    }

    *pCtx = pDataCtx;

    return s32Ret;

EXIT:
    if(pDataCtx != NULL) {
        free(pDataCtx);
    }
    pDataCtx->bRunStatus = false;

    return s32Ret;
}

int app_ipcam_LList_Data_DeInit(void * *pCtx)
{
    if ((pCtx == NULL) || (*pCtx == NULL)) {
        printf("pCtx or *pCtx is NULL\n");
        return -1;
    }

    APP_DATA_CTX_S *pstDataCtx = *pCtx;
    APP_DATA_PARAM_S *pstDataParam = &pstDataCtx->stDataParam;

    void *pDataDrop = NULL;
    pstDataCtx->bRunStatus = false;

    pthread_join(pstDataCtx->pthread_id, NULL);

    while(app_ipcam_LList_Data_Pop(&pDataDrop, *pCtx) == 0) {
        if(pDataDrop) {
            if(pstDataParam->fpDataFree) {
                pstDataParam->fpDataFree(&pDataDrop);
            }
            pDataDrop = NULL;
        }
    }
    pstDataCtx->LListDepth = 0;
    pthread_mutex_destroy(&pstDataCtx->mutex);

    free(*pCtx);
    *pCtx = NULL;

    return 0;
}

应用实例

数据处理模块初始化,初始化完成后会创建数据处理线程,根据线程参数(包括多个数据消费者函数指针)做数据处理

            APP_DATA_PARAM_S stDataParam = {0};
            stDataParam.pParam = (void *)pstVencChnCfg;
            stDataParam.fpDataSave = _Data_Save;
            stDataParam.fpDataFree = _Data_Free;
            stDataParam.fpDataHandle = _Data_Handle;
            stDataParam.fpDataConsumes[0] = fpStreamingSendToRtsp;
            stDataParam.fpDataConsumes[1] = (VencChn == APP_VENC_STREAM_SUB) ? fpStreamingSendToWeb : NULL;
            stDataParam.fpDataConsumes[2] = (VencChn == APP_VENC_STREAM_MAIN) ? fpStreamingSendToRecord : NULL;
            stDataParam.fpDataConsumes[3] = (VencChn == APP_VENC_STREAM_MAIN) ? fpStreamingSaveToFlash : NULL;

            s32Ret = app_ipcam_LList_Data_Init(&g_pDataCtx[VencChn], &stDataParam);// 第一个参数g_pDataCtx是一个全局的指针,第二个参数为数据处理的参数
            if (s32Ret != CVI_SUCCESS) {
                APP_PROF_LOG_PRINT(LEVEL_ERROR, "Link list data init failed with %#x\n", s32Ret);
                goto VENC_EXIT1;
            }
 
接在在自己的数据生产线程中调用  app_ipcam_LList_Data_Push 做数据压栈处理
 
Notes:
  • 数据缓存深度可以根据 LL_DATA_CACHE_DEPTH_MAX 来自定义
  • 如果 LL_DATA_CACHE_DEPTH_MAX 定义过大,可能会消耗过大的内存
  • 如果 LL_DATA_CACHE_DEPTH_MAX必须要定义过大,可能要先检查整个数据流是否合理
  • 此机制只能做数据通路中偶尔的后端处理过久情况,比如推流到网络且网络不好时

linux内核链表的使用可以参考这里

posted @ 2022-10-23 21:05  X-Long  阅读(120)  评论(0)    收藏  举报