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环境下,这些函数被广泛用于处理各种输入。本文将详细介绍这三个函数的用法,包括格式化字符串的语法和一些常见的使用场景。
1607 1
hutool 判断字符串是否全部为字母组成,包括大写和小写字母和汉字
hutool 判断字符串是否全部为字母组成,包括大写和小写字母和汉字
|
Ubuntu
Ubuntu配置Udev规则固定设备口
本文介绍了如何在Ubuntu系统中通过配置udev规则来固定设备的端口,详细说明了如何查看设备信息、创建udev规则文件、设置设备权限,并加载及重启udev规则以应用更改。
2137 0
Ubuntu配置Udev规则固定设备口
|
存储 安全 Linux
【实战指南】7个设置/获取接口了解Linux时间管理
本文系统介绍了Linux时间管理中的7个关键设置/获取接口,涵盖时间获取(如`time`、`gettimeofday`、`clock_gettime`)、时间设置(如`stime`、`settimeofday`、`clock_settime`)以及时间转换和格式化等内容。文章详细解析了绝对时间和相对时间的概念,包括GMT、UTC及本地时间的区别,并通过实例测试展示了各接口的使用方法与特性。此外,还探讨了时区设置对时间计算的影响,强调在实际开发中推荐使用UTC作为基准时间以避免时区变化带来的问题。总结部分结合项目经验,提醒开发者注意时间服务的重要性及潜在风险,例如时间跳跃可能引发的应用故障。
1076 132
【实战指南】7个设置/获取接口了解Linux时间管理
|
编解码 算法 网络协议
流量控制--5.Classless Queuing Disciplines (qdiscs)
流量控制--5.Classless Queuing Disciplines (qdiscs)
478 4
|
机器学习/深度学习 分布式计算 并行计算
推荐一些机器学习系统MLSys中的值得研究的方向
MLsys不能算是一种方向,而是一种思路。比如对于system研究者来说,可以把ML作为我们开发的系统要适配的一种benchmark,就像transaction对于数据库、某种文件场景对于File System的意义一样。这样一想可做的空间就宽广多了
2132 0
|
数据挖掘 Linux
如何在 Linux 中将输出重定向到文件和标准输出?如此简单!
如何在 Linux 中将输出重定向到文件和标准输出?如此简单!
3815 0
如何在 Linux 中将输出重定向到文件和标准输出?如此简单!
使用npm,快速构建第一个vue项目
本文介绍了如何使用npm快速构建第一个Vue项目。步骤包括确保安装了Node.js并且配置了正确的环境变量,创建一个空文件夹并使用VSCode打开,通过VSCode的终端执行`npm init vue@latest`命令以初始化项目,选择默认配置即可。接着安装项目依赖并启动开发服务器,最后通过浏览器访问开发服务器提供的本地地址查看项目运行结果。文章还提供了相关代码和操作截图。
|
开发工具 git
GIT:如何合并已commit的信息并进行push操作
通过上述步骤,您可以有效地合并已提交的信息,并保持项目的提交历史整洁。记得在执行这些操作之前备份当前工作状态,以防万一。这样的做法不仅有助于项目维护,也能提升团队协作的效率。
1315 5
|
Kubernetes 网络安全 容器
在K8S中,有个服务使用service的nodeport进行暴露,发现访问不到如何排查?
在K8S中,有个服务使用service的nodeport进行暴露,发现访问不到如何排查?

热门文章

最新文章