一百多行 C 语言代码实现一个简单异步事件触发机制!

news2024/9/22 5:28:08

一、简介

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行),实现异步操作,解决项目性能问题

libevent 解决了网络编程中哪些痛点?

免费学习地址:c/c++ linux服务器开发/后台架构师

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

三、使用说明

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 语言代码实现一个简单异步事件触发机制!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/799428.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于穷举搜索的AI自动实现俄罗斯方块游戏(核心代码详解)

一、算法思想核心 这个俄罗斯方块AI的核心思想是通过穷举搜索当前形状和下一个形状的所有可能的放置组合&#xff0c;并为每个组合计算一个得分。得分是根据游戏板面的状态来评估每个组合的优劣&#xff0c;最终选择得分最高的放置策略。 具体的实现步骤和核心思想如下&#…

怎么恢复回收站清空的文件,教你三种恢复的方法

不论是笔记本电脑还是台式机电脑&#xff0c;都有回收站这样的“后悔药”&#xff0c;我们许多误删除的文件夹、图片、视频、文档等都能从它里面找到&#xff0c;回收站在灵活运用下能更好地为我们服务&#xff0c;但是使用过程中难免会出现失误&#xff0c;比如说不小心清空了…

pdf合并大小不一样怎么办?有这几个方法就够了

pdf合并大小不一样怎么办&#xff1f;在日常工作和生活中&#xff0c;我们经常需要处理PDF文件。在将多个PDF文件合并成一个时&#xff0c;由于这些文件的大小和格式可能不同&#xff0c;可能会遇到一些问题。但不用担心&#xff0c;接下来将介绍几种方法来解决这个问题。 方法…

JAVA缓存技术介绍

OSCache OSCache是个一个广泛采用的高性能的J2EE缓存框架&#xff0c;OSCache能用于任何Java应用程序的普通的缓存解决方案。 OSCache有以下特点&#xff1a; 我创建了一个群&#xff0c;群里不定期分享技术干货&#xff0c;行业秘闻&#xff0c;汇集各类奇妙好玩的话题和流行动…

Individual household electric power consumption个人家庭用电量数据挖掘与时序预测建模

今天接到一个任务就是需要基于给定的数据集来进行数据挖掘分析相关的计算&#xff0c;并完成对未来时段内数据的预测建模&#xff0c;话不多少直接看内容。 官方数据详情介绍在这里&#xff0c;如下所示&#xff1a; 数据集中一共包含9个不同的字段&#xff0c;详情如下&#…

MinIO对象存储

MinIO 是一个高性能的分布式对象存储系统。 它是软件定义的&#xff0c;在行业标准硬件上运行&#xff0c;并且 100% 开源&#xff0c;主要许可证是 GNU AGPL v3。 MinIO 的不同之处在于它从一开始就被设计为私有/混合云对象存储的标准。 因为 MinIO 是专门为对象而构建的&…

day14 | 100.二叉树的最大深度 111.二叉树的最小深度 222.完全二叉树的节点个数

文章目录 一、二叉树的最大深度二、二叉树的最小深度三、完全二叉树的节点数 一、二叉树的最大深度 100.二叉树的最大深度 因为题给出的高度和深度是一样的&#xff0c;所以求得结果都能过。 class Solution { public:int getHeight(TreeNode *node){if (node nullptr)retu…

Qt 第三讲 手动拖动ui界面组件,事件处理机制,实现简单闹钟

手动拖动ui界面组件&#xff0c;实现闹钟 源文件 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);ui->btn_end->setEnabled(false);speecher new QTex…

Linux基础内容(29)—— 额外锁

Linux基础内容&#xff08;28&#xff09;—— POSIX信号量与循环队列_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131844590?spm1001.2014.3001.5501 目录 1.其他常见的各种锁 自旋锁 库语言的实现 2.读者写者问题 1.读者写者线程 2.…

SpringMVC启动时非常缓慢,显示一直在部署中,网页也无法访问,,,Artifact is being deployed, please wait...

写了一个基本的SpringMVC程序进行测试&#xff0c;结果启动时一直显示在等待部署完毕&#xff0c;&#xff0c;&#xff0c; but这个地方一直显示转圈圈。。 后来通过url访问时网页一直转圈圈。。也就是等待响应。。 看了一会儿&#xff0c;也不知道哪儿错了&#xff0c;&…

什么是多运行时架构?

服务化演进中的问题 自从数年前微服务的概念被提出&#xff0c;到现在基本成了技术架构的标配。微服务的场景下衍生出了对分布式能力的大量需求&#xff1a;各服务之间需要相互协作和通信&#xff0c;以及共享状态等等&#xff0c;因此就有了各种中间件来为业务服务提供这种分…

紫光FPGA试用--软件篇

目录 一 软件安装启动 二 如何打开IP核&#xff1f;查看/修改现有IP核参数&#xff1f; 三 如何定义引脚&#xff1f; 四 如何下载code进入FPGA? 1. 下载到FPGA芯片内&#xff1a; 2.下载到外部FLASH中 五 如何进入在线调试模式&#xff0c;调试步骤 操作步骤&#xff…

微分流形之魂

找到一个非常棒的教程&#xff1a;本科生自学微分流形有哪些资料推荐&#xff1f; - 知乎 应该是目前微积分的终极答案了&#xff08;非数学系&#xff09; 首先&#xff0c;这个函数具有线性结构。所以他是属于V*的。 之前我倒没想过这个问题&#xff0c;以为所有的泛函都是V…

4个公式8个案例学会与AI GPT对话

一、什么是Prompt? (1)Prompt的概念 我们在使用GPT的时候,给GPT发送的消息就是Prompt. 例如,当我们问ChatGPT"WPS是什么软件?"时: 其中,"WPS是什么软件?"这句话就是Prompt. chatgpt体验&#xff1a;http://www.chat136.com chatgpt学习&#xff1a…

【OJ比赛日历】快周末了,不来一场比赛吗? #07.29-08.04 #13场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2023-07-29&#xff08;周六&#xff09; #8场比赛2023-07-30…

一文带你迅速入门SprIngMVC,看这一篇就足够了!

0. 什么是SpringMVC 要知道什么是SpringMVC&#xff0c;我们首先得知道什么 MVC&#xff0c;MVC是软件工程中的一种架构模式&#xff0c;分为 Model、View、Control。它把软件系统分为模型、视图和控制器三个基本部分。 Model&#xff1a;模型&#xff0c;应用程序负责数据逻…

SVN - 记录一下无法提交代码 提示:被锁定(locked)

今天遇到一个问题&#xff0c;svn 在提交代码的时候出现了svn is already locked&#xff0c;导致代码无法提交&#xff08;commit&#xff09;和更新&#xff08;update&#xff09; 主要报错如下&#xff1a; 解决方法&#xff1a; 然后点击 Clean up 选中一下选项&#xff…

人工智能巨头碰撞——埃隆·马斯克推出xAI挑战OpenAI的统治地位

目录 前言XAI 的推出什么是XAI&#xff1f;它将聚焦于什么&#xff1f; 一&#xff1a;“反AI斗士”马斯克进军AI&#xff0c;你怎么看&#xff1f;二&#xff1a;回顾上半年的“百模大战”&#xff0c;中国的AI产业怎么样了&#xff1f;三&#xff1a;AI大模型这把火&#xff…

算法通过村第二关-链表白银笔记

文章目录 再战链表|反转链表剑指 Offer II 024. 反转链表熟练掌握这两种解法建立头节点的解决思路不采用建立头节点的方法采用循环/递归的方式解决 总结 再战链表|反转链表 提示&#xff1a;多拿些酒来&#xff0c;因为生命只有乌有。 剑指 Offer II 024. 反转链表 如果不使用…

9个可用于图片转文本的最佳免费 OCR 软件

光学字符识别 (OCR) 软件可帮助将不可编辑的文档格式&#xff08;例如 PDF、图像或纸质文档&#xff09;转换为可编辑和可搜索的机器可读格式。 OCR 应用程序通常用于从 PDF 和图像中捕获文本&#xff0c;并将文本转换为可编辑格式&#xff0c;例如 Word、Excel 或纯文本文件。…