关于dpnet项目

关于dpnet项目

dpnet是我开源的一个轻量异步框架,主要用于利用多核优势执行异步任务,处理异步IO。

起初并没有独立的dpnet项目,所有功能集成在另一个项目dplua中。

提到异步,实现方案必然是回调或协程二选其一。基于回调的异步网络库以libuv,libevent最广为人知。

在协程兴起后,现在更多的开发者选择使用协程处理异步。或使用go这种天然支持协程的语言。

而Lua的协程实现是我非常喜欢的。所以就开始了dplua开发。

后来我参考了一些C语言的协程实现,实现了当前dpnet使用的协程库。基于该协程库,将原dplua中的核心部分独立出来并构建了dpnet项目。

而现在dplua使用dpnet除协程外的核心部分,dplua反倒成了dpnet的附属项目。

异步IO

在Linux下,异步IO的主要实现方式是epoll,poll,select。在新版本Linux内核中,更是提供了io_uring这种新技术。相信io_uring以后会成为Linux平台下异步IO的主流选择,目前已经出现了一些基于此的库或框架。

dpnet仍然使用了epoll方案,并且计划支持kqueue。其实我非常期待FreeBSD中io_uring的实现,所以未来FreeBSD的io_uring公布时,会正式添加io_uring的支持。

dpnet包装了一个dpepfd的结构,使用非阻塞fd + 协程,可以很方便的实现异步IO。在发出IO请求时,如果fd没有数据,或数据不足,dpepfd会记录数据,IO位置等信息,这在流式IO中主要依赖dpbuf。之后会将协程挂起,dpevp内部在轮询到事件后自动执行IO操作,直到IO完成或出错,唤醒协程。

原理其实看上去很简单,实际使用时需要注意阻塞操作会大大降低整体的性能。

多线程

dpnet使用多线程,他不是线程池,所有的线程都在dpnet启动时指定并创建。之后线程内部调用模块中的对应初始化函数,开始执行逻辑。

dpnet首先定义的是线程类型,你可以把它理解为线程组,每一类线程都可以通过命令行参数指定其需要创建的线程数量。不同类线程都执行不同的模块入口函数。这在项目的README.md文档中有介绍。

但类型和线程数量不能无限大,所以根据一些经验,以及为了之后分布式部署准备,dpnet限制每个dpnet实例最多有63个线程类型,线程总数不能超过255个。对于应用和CPU而言,这个限制应该已经足够了。如果不够可以再启动新的dpnet实例。

一个线程被创建后,首先会初始化专属这个线程的事件循环,然后初始化协程环境,最后在协程中执行模块的初始化函数。

如果在命令行中指定某类型线程数量为0,则该类型下不会创建线程,但是该类型会附加到master线程中,并在master线程执行该类型线程的初始化方法。

实际上每个线程都是独立的,他们不共享任何数据。他们之间唯一的联系就是通过master线程传递异步任务。

异步任务

在README.md文档中介绍了一部分任务实现的细节。其实这部分主要分歧集中在任务队列的实现上,比如lockfree的队列,或者lockfree的环形队列。这在boost的lockfree模块中已经有了实现。尤其是lockfree环形队列的性能是非常高的。

但是这些队列的问题在于没有通知机制。事件循环不能没有计划的轮询,这会大大浪费CPU,但是不轮询没有办法及时知道队列上有没有消息。eventfd可以满足在epoll中实现事件通知。如何将队列和eventfd结合起来,变成一个通知队列?答案就是现在dpnet的实现。

经过不严谨的测试,dpnet实现的队列比boost lockfree队列性能要高很多。但比lockfree环形队列性能低不少。这算是一个性能和功能上相对平衡的实现。

可以干什么

说了这么多,dpnet可以干什么是个问题。

其实就我个人工作而言,最需要的就是基于异步任务,构建异步执行DAG的调度系统。可以将不同的类型的任务分配到不同的线程中执行。比如:耗时任务可以分配到01线程组,IO任务可以分配到02线程组,等等。并且可以根据业务场景的不同,调整不同组中线程的数量。很显然目前的dpnet是可以满足这个需求的,接下来我希望它能够支持集群,将任务自动分配到不同的机器上执行。

你也可以使用dpnet实现网络服务器,游戏服务器,客户端等等,我写了一个dpresp,供有兴趣的同学参考。

甚至也可以集成glfw,imgui等库,构建一个图形化的应用。等我有空了会写一个demo。

或者说作为一个轻量的异步框架,它有很多种可能。