libev与多线程

简介: libev与多线程

libev与多线程

libev支持多线程,也是one loop per-thread的模型。

但是要手动的启动和管理多个libev的细节。

我采用的方式是: N个libev的数据套接字线程 + 1个libev的监听套接字线程。

即,1个主线程里跑1个libev,称为main_libev。负责驱动accept socket,同时还注册1个timer用于定时统计链接相关的数据。

其他N个线程里分别各自的libev,称为data_libev,负责驱动数据的socket。

image.png

当有新连接到来时,main_libev的回调里通过enqueue操作把新的fd传给N个线程的任意一个,这个操作是线程安全的。

同时给这个线程发送一个异步事件(async_send操作),告知其可以接客了。

data_libev在async的回调里,取出新的fd,然后加入到自己的ev中,并设置相应的回调。

data_libev的封装

data_libev被封装在类中,同时提供ev_run(),receive_fd(),add_newfds()等操作。

typedef std::list<int> FDList;
typedef FDList::iterator FDItr;
class LibEVPerThread
{
public:
    LibEVPerThread()
    {
        m_n_fd_received = 0;
        m_n_fd_added = 0;
        pthread_mutex_init(&m_lock, NULL);
        m_evloop_ptr = ev_loop_new(EVBACKEND_EPOLL);
    }
    bool set_id(unsigned int id) { m_id = id; }
    int ev_run()
    {
        ev_async_init(&m_async, async_cb);
        ev_async_start(m_evloop_ptr, &m_async);
        ev_set_userdata(m_evloop_ptr, this);
        printf("begin to ev_run()\n");
        ::ev_run(m_evloop_ptr, 0);
        printf("end to ev_run()\n");
    }
    bool receive_fd(int fd)
    {
        printf("ID: %u ----> receive a fd:%d\n", m_id, fd);
        enque_fd(fd);
        ev_async_send(m_evloop_ptr, &m_async);
    }
    int add_newfds()
    {
        FDList tmpfds = deque_fd();
        for (FDItr itr=tmpfds.begin(); itr!=tmpfds.end(); ++itr)
        {
            m_n_fd_added ++;
            add_fd(*itr);
            printf("ID: %u ----> added a fd:%d, totoal: %u\n", m_id, *itr, m_n_fd_added);
        }
    }
private:
    int enque_fd(int fd)
    {
        pthread_mutex_lock(&m_lock);
        m_sockfds.push_back(fd);
        pthread_mutex_unlock(&m_lock);
    }
    FDList deque_fd()
    {
        FDList tmp;
        pthread_mutex_lock(&m_lock);
        tmp.swap(m_sockfds);
        pthread_mutex_unlock(&m_lock);
        return tmp;
    }
    bool add_fd(int fd)
    {
        struct ev_io *evio_ptr = new ev_io();
        ev_io_init(evio_ptr, message_cb, fd, EV_READ);
        ev_io_start(m_evloop_ptr, evio_ptr);
    }
private:
    pthread_mutex_t m_lock;
    FDList m_sockfds;
    struct ev_async m_async;
    struct ev_loop *m_evloop_ptr;
    pthread_t m_tid;
    unsigned int m_n_fd_added;
    unsigned int m_n_fd_received;
    unsigned int m_id;
};

ev_run() 启动libev。

receive_fd() 提供给外界的接口,负责在新的fd到来后,添加到这个类中,并同时libev。

add_newfds() 提供给自己使用,负责在async的回调中,把新的fd添加到到自己的libev中。

ev_run()启动的时候只有一个async的事件,在它的回调里调用libev类的add_newfds()。

void async_cb(EV_P_ ev_async *w, int events)
{
    EVThreadInfo *ev_thread_info = (EVThreadInfo*)ev_userdata(EV_A);
    ev_thread_info->add_newfds();
}

main_libev

在main()的线程里使用EV_DEFAULT,监视accept socket的事件。

struct ev_loop *main_evloop = EV_DEFAULT;
    int accept_fd = init_accept_sock();
    ev_io accept_ev;
    ev_io_init (&accept_ev, on_connection_cb, accept_fd, EV_READ);
    ev_io_start (main_evloop, &accept_ev);

在它的回调函数on_connection_cb里,首先accept接收一个新的套接字,然后robin的方式选择一个libev类,调用libev类的receive_fd,把新的套接字发送给libev类,并通知。

void on_connection_cb(EV_P_ ev_io *w, int events)
{
     int newfd = accept(w->fd, NULL, 0);
     EVThreadInfo* ev_thread_info = &g_thread_info_prt[g_robin%g_n_thread];
     ev_thread_info->receive_fd(newfd);
     g_robin++;
}

实验和待处理问题

启动服务端

[zunbao.fengzb@rt1f07470.tbc /home/zunbao.fengzb/echo_client_server_libev]
$./multi_loop_server 6089 24

启动客户端

[CHECK@ump137035.sqa.cm4 ~/rds_proxy_test/proxy/echo_client_server_libev]
$./multi_loop_client 10.98.109.161 6089 24 85

实验结果

in timer callback, qps 1026115
in timer callback, qps 1016615
in timer callback, qps 1025216

client每次发送1个字节。

每个数据包的大小= 1Byte载荷 + IP头部20Byte + tcp头部20Byte+tcp选项12Byte = 53Byte

同时,每个数据包都要回一个ack包,ack包大小= IP头部20Byte + tcp头部20Byte = 40Byte

相关文章
|
Linux C语言 开发者
深入解析Linux环境下的scanf()、sscanf()和fscanf()函数
在C语言中,`scanf()`、`sscanf()`和`fscanf()`是用于输入的三个常用函数。它们允许开发者从标准输入、字符串和文件中按照指定的格式读取数据。在Linux环境下,这些函数被广泛用于处理各种输入。本文将详细介绍这三个函数的用法,包括格式化字符串的语法和一些常见的使用场景。
1683 1
|
存储 编译器 C语言
详解C/C++中的static和extern
本文详解了C/C++中`static`和`extern`关键字的用法和区别,通过具体代码示例说明了在不同情境下如何正确使用这两个关键字,以及`extern "C"`在C++中用于兼容C语言库的特殊作用。
785 4
详解C/C++中的static和extern
|
JavaScript 物联网 开发者
NB-IoT 之 CoAP 开源 libcoap 服务器客户端的安装使用 | 学习笔记
快速学习 NB-IoT 之 CoAP 开源 libcoap 服务器客户端的安装使用
NB-IoT 之 CoAP 开源 libcoap 服务器客户端的安装使用 | 学习笔记
|
存储 安全 Linux
【实战指南】7个设置/获取接口了解Linux时间管理
本文系统介绍了Linux时间管理中的7个关键设置/获取接口,涵盖时间获取(如`time`、`gettimeofday`、`clock_gettime`)、时间设置(如`stime`、`settimeofday`、`clock_settime`)以及时间转换和格式化等内容。文章详细解析了绝对时间和相对时间的概念,包括GMT、UTC及本地时间的区别,并通过实例测试展示了各接口的使用方法与特性。此外,还探讨了时区设置对时间计算的影响,强调在实际开发中推荐使用UTC作为基准时间以避免时区变化带来的问题。总结部分结合项目经验,提醒开发者注意时间服务的重要性及潜在风险,例如时间跳跃可能引发的应用故障。
1191 134
【实战指南】7个设置/获取接口了解Linux时间管理
|
11月前
|
运维 数据安全/隐私保护 内存技术
NVMe 盘故障排查 5 步速查
通过系统日志、NVMe健康状态、错误日志及Pangu集群摘要等多维度诊断NVMe盘故障,判断是否持续报错、介质损坏或文件系统异常,结合硬件与集群状态精准定位问题,指导及时换盘或观察处理。
768 0
|
开发工具 git
GIT:如何合并已commit的信息并进行push操作
通过上述步骤,您可以有效地合并已提交的信息,并保持项目的提交历史整洁。记得在执行这些操作之前备份当前工作状态,以防万一。这样的做法不仅有助于项目维护,也能提升团队协作的效率。
1389 5
|
Linux 开发工具 索引
基于Linux系统的本地Yum源搭建与配置(ISO方式、RPM方式)
基于Linux系统的本地Yum源搭建与配置(ISO方式、RPM方式)
2686 0
使用npm,快速构建第一个vue项目
本文介绍了如何使用npm快速构建第一个Vue项目。步骤包括确保安装了Node.js并且配置了正确的环境变量,创建一个空文件夹并使用VSCode打开,通过VSCode的终端执行`npm init vue@latest`命令以初始化项目,选择默认配置即可。接着安装项目依赖并启动开发服务器,最后通过浏览器访问开发服务器提供的本地地址查看项目运行结果。文章还提供了相关代码和操作截图。
|
编解码 Dubbo NoSQL
由浅入深理解RPC架构设计
🌴🌴经常听到大家说不同项目之间调用使用HTTP方式,同一个项目内不同服务之间调用使用RPC方式。今天就来学习一下`RPC框架`,RPC框架由哪些部分组成又是如何一步一步设计出来的。
1285 0
由浅入深理解RPC架构设计
|
消息中间件 负载均衡 监控
RabbitMQ精讲9:镜像模式集群搭建,整合KeepAlived+HaProxy组件
RabbitMQ精讲9:镜像模式集群搭建,整合KeepAlived+HaProxy组件
1272 122
RabbitMQ精讲9:镜像模式集群搭建,整合KeepAlived+HaProxy组件

热门文章

最新文章