天天看點

一百多行 C 語言代碼實作一個簡單異步事件觸發機制!

作者:linux技術棧

一、簡介

QT 中有一種異步處理機制叫做信号和槽函數,通過将信号與槽函數進行綁定連接配接,後續若該信号觸發,會自動調用對應的槽函數。這種機制很适合處理很繁瑣的邏輯程式,例如我點選界面的 close 按鈕,便觸發 close 信号,自動調用 close 綁定的槽函數,關閉界面。這種使用流程簡便快捷。這種處理機制可稱作異步處理,C 語言中也有一些異步處理開源的庫,例如 libevent、libev 等,前者功能豐富,技術架構較為成熟,在許多項目中都見到它身影。這些開源庫成熟,但是也龐大,能不能搞一個簡潔的異步事件庫呢?接下來我們就實作一個簡單異步事件處理。

二、設計實作

我做的是一個簡單異步事件,根據信号觸發對應事件,實作原理很簡單:1.綁定信号和對應的回調函數; 2.檢測信号隊列或者連結清單,若有信号觸發,便取對外連結表中的節點處理對應的回調函數。本設計中采用是雙向連結清單存儲信号,為了友善(偷懶),就不自己造連結清單的輪子了,這裡使用 Linux 核心源碼中的雙向連結清單(list.h)。

1.結構體定義

這個eventinfo_t結構體裡面包含對應的信号值和函數指針。

typedef struct eventinfo_t
{
    void (*func)(void* args); /* 事件回調函數 */
    int sig;                  /* 信号值 */  
}eventinfo_t;           

這個eventlist_t結構體是定義信号連結清單的,裡面包含了觸發信号時候傳遞的參數,信号值,一個連結清單。

typedef struct eventlist_t
{
    void *args;            /* 傳遞的參數 */
    int sig;               /* 信号值 */
    struct list_head list; /* 雙向連結清單 */
}eventlist_t;           

這個asyncevent_t結構體是異步事件處理句柄定義,包含所有資訊。

typedef struct asyncevent_t
{
    struct list_head hlist; /* 信号連結清單頭 */
    eventinfo_t map[1024]; /* 信号與函數映射 */
    #if USE_MUTI_THREAD   /* 是否使用多線程 */
    pthread_mutex_t lock; /* 互斥鎖 */
    pthread_cond_t cond;  /* 條件變量 */
    #endif
}asyncevent_t;           

2.定義的函數

這是定義的所有函數,每個函數都有具體的注釋,如下:

/**
 * @brief:  異步事件綁定信号和回調函數
 * @handle: 事件句柄
 * @sig:  信号值
 * @func: 處理該信号的函數
 * @return: 0:成功
*/
int async_event_bind(asyncevent_t* handle, int sig, void (*func)(void* args))//綁定信号和函數
{
    if(!handle || !func) return -1;     /* 句柄不存在 */
    if(sig<0 || sig>1024) return -2;    /* 信号值超過有效範圍 */
    if(handle->map[sig].func) return -3;/* 信号以及被綁定過了 */

    handle->map[sig].func = func;       /* 綁定函數 */
    handle->map[sig].sig = sig;         /* 綁定信号 */

    return 0;
}

/**
 * @brief: 發射信号,觸發事件
 * @priority: 0:添加到連結清單尾部 非0:添加到連結清單頭部(能及時響應)
 * @sig: 對應的信号
 * @args: 傳遞的參數
 * @return: 0:成功
*/
int async_event_emit(asyncevent_t* handle, int priority, int sig, void* args) 
{
    if(!handle) return -1;              /* 事件句柄不存在 */
    if(sig<0 || sig>1024) return -2;    /* 信号值超過有效範圍 */
    if(!handle->map[sig].func) return -3; /* 該信号未綁定,不能觸發事件 */
     
    eventlist_t* node = (eventlist_t*)malloc(sizeof(eventlist_t));
    if(!node) return -1;
    node->args = args;
    node->sig = sig;

    #if USE_MUTI_THREAD
    pthread_mutex_lock(&handle->lock);
    #endif

    if(priority)
        list_add(&node->list, &handle->hlist);      /* 往頭部添加 */
    else
        list_add_tail(&node->list, &handle->hlist); /* 往尾部添加 */
    #if USE_MUTI_THREAD
    pthread_cond_signal(&handle->cond);
    pthread_mutex_unlock(&handle->lock);
    #endif

    return 0;
}

/**
 * @brief: 排程處理所有的事件任務
 * @handle: 事件句柄
 * @return: 0:成功
*/
int async_event_process(asyncevent_t* handle) //排程處理
{
    if(!handle) return -1;
    struct list_head *pos, *n;
    eventlist_t *node;
    int sig = 0;

    #if USE_MUTI_THREAD
    pthread_mutex_lock(&handle->lock);
    while (list_empty(&handle->hlist)){
        pthread_cond_wait(&handle->cond, &handle->lock);
    }
    pthread_mutex_unlock(&handle->lock);
    #endif

    list_for_each_safe(pos, n, &handle->hlist){
        node = list_entry(pos, eventlist_t, list);
        #if USE_MUTI_THREAD
        pthread_mutex_lock(&handle->lock);
        list_del(&node->list); /* 從連結清單中删除節點 */
        pthread_mutex_unlock(&handle->lock);
        #else
        list_del(&node->list); /* 從連結清單中删除節點 */
        #endif
        sig = node->sig;
        handle->map[sig].func(node->args); /* 調用事件函數 */

        free(node);
    }
    
    return 0;
}

/**
 * @brief: 建立一個異步事件處理句柄
 * @return:異步事件句柄
*/
asyncevent_t* create_async_event(void)
{
    asyncevent_t *handle = (asyncevent_t*)malloc(sizeof(asyncevent_t));
    if(!handle) return NULL;
    memset(handle, 0, sizeof(handle));
    INIT_LIST_HEAD(&handle->hlist);
    #if USE_MUTI_THREAD
    pthread_mutex_init(&handle->lock, NULL);
    pthread_cond_init(&handle->cond, NULL);
    #endif
    return handle;
}

/**
 * @brief: 釋放異步事件資源
 * @return: 0:成功
*/
int async_event_destory(asyncevent_t* handle)
{
    if(!handle) return -1;
    struct list_head *pos, *n;
    eventlist_t *node;

    #if USE_MUTI_THREAD
    pthread_mutex_lock(&handle->lock);
    #endif
    list_for_each_safe(pos, n, &handle->hlist){
        node = list_entry(pos, eventlist_t, list);
        list_del(&node->list); /* 從連結清單中删除節點 */
        free(node);
    }
    #if USE_MUTI_THREAD
    pthread_mutex_unlock(&handle->lock);
    pthread_mutex_destroy(&handle->lock);
    pthread_cond_destroy(&handle->cond);
    #endif

    free(handle);

    return 0;
}           

相關視訊推薦

程式性能優化-異步解決80%的問題

手把手實作線程池(120行),實作異步操作,解決項目性能問題

從 4 個方面了解 libevent 的原理及使用

需要C/C++ Linux伺服器架構師學習資料加qun812855908擷取(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享

一百多行 C 語言代碼實作一個簡單異步事件觸發機制!

三、使用說明

1.編譯運作

目标平台:Linux;編譯器:GCC。輸入:make 進行編譯,輸入:make clean 清除生成檔案,輸入:./mainApp 執行本例中的測試程式。

2.使用流程

通過修改 asyncevent.h 檔案中#define USE_MUTI_THREAD 1 的宏定義決定是否使用多線程,為 1 使用多線程。

a.單線程模式測試

首先定義枚舉類型的信号(也可不定義,直接寫數值,為了規範還是建議定義),編寫對應的事件處理函數如void event_click_func(void *args),然後建立句柄,綁定信号,在 while 循環裡面調用async_event_process(handle);處理函數,至于信号什麼時候發射完全由外部決定,本例直接在循環裡面一直發射信号。注:不能在自己信号處理函數中發射自己信号,這樣會導緻一直循環發射處理,造成死循環,但是可以發射除自身以外的其他信号。

enum SIG_TYPE //信号類型
{
    CLICK=1,  //單擊
    MOVE,     //拖動
    PRESS,    //按下
    RELEASE,  //釋放
};

asyncevent_t* handle;

void event_click_func(void *args)
{
    printf("Click Event Trigger, Times=%d...\n", *(int*)args);

    //可以在事件處理函數中觸發其他的信号!
    //不能在自己事件函數中觸發自己,這樣會一直循環觸發自己,造成死循環!
    // async_event_emit(handle, 1, CLICK, args); //錯誤
    async_event_emit(handle, 1, MOVE, args);
    async_event_emit(handle, 1, PRESS, args);
    async_event_emit(handle, 1, RELEASE, args);
}

void event_move_func(void* args)
{
    printf("Move Event Trigger, Times=%d...\n", *(int*)args);
}

void event_press_func(void *args)
{
    printf("Press Event Trigger, Times=%d...\n", *(int*)args);
}

void event_release_func(void *args)
{
    printf("Release Event Trigger, Times=%d...\n", *(int*)args);
}

int main(int argc, char **argv)
{
    int cnt = 0;
    //1.建立事件句柄
    handle = create_async_event();

    //2.綁定信号
    async_event_bind(handle, CLICK, event_click_func);
    async_event_bind(handle, MOVE, event_move_func);
    async_event_bind(handle, PRESS, event_press_func);
    async_event_bind(handle, RELEASE, event_release_func);

    //3.循環排程執行
    while(1)
    {
        async_event_process(handle);
        async_event_emit(handle, 0, CLICK, &cnt);
    }

    return 0;
}           

b.多線程模式測試

處理 main 函數與上面不同,其他定義是一樣的。本例使用多線程測試,開啟一個線程一直調用async_event_process(handle)處理函數,然後 main 函數中采用輸入 a-c 字元觸發信号。

void* process_event_thread(void *args)
{
    asyncevent_t* handle = (asyncevent_t*)args;
    
    //循環排程執行
    while(1)
    {
        async_event_process(handle);
    }
}

int main(int argc, char** argv)
{
    pthread_t th;
    char c=0;
    int cnt_click = 0, cnt_move=0, cnt_press=0, cnt_release=0;

    //1.建立事件句柄
    handle = create_async_event();

    //2.綁定信号
    async_event_bind(handle, CLICK, event_click_func);
    async_event_bind(handle, MOVE, event_move_func);
    async_event_bind(handle, PRESS, event_press_func);
    async_event_bind(handle, RELEASE, event_release_func);

    //3.建立一個線程去處理事件
    pthread_create(&th, NULL, process_event_thread, handle);

    while(1)
    {
        //4.根據自己時機去觸發信号
        scanf("%c", &c);
        switch (c)
        {
        case 'a':
            cnt_click++;
            async_event_emit(handle, 0, CLICK, &cnt_click);
            break;
        case 'b':
            cnt_move++;
            async_event_emit(handle, 0, MOVE, &cnt_move);
            break;
        case 'c':
            cnt_press++;
            async_event_emit(handle, 0, PRESS, &cnt_press);
            break;
        case 'd':
            cnt_release++;
            async_event_emit(handle, 0, RELEASE, &cnt_release);
            break;
        default:
            break;
        } 
        
    }

    pthread_join(th, NULL);

    return 0;
}           

輸入a,先觸發click信号,然後在click處理函數中發射release、press、move等信号,繼續觸發對應的處理函數。輸入b單獨觸發move信号,輸入c單獨觸發press信号,輸入d單獨觸發release信号。

一百多行 C 語言代碼實作一個簡單異步事件觸發機制!

這個異步事件處理程式還不夠完善,歡迎大家嘗試運作一下。

原文位址:一百多行 C 語言代碼實作一個簡單異步事件觸發機制!

繼續閱讀