Libev
的事件循环对象由struct ev_loop
定义.
此函数将返回”默认“的ev_loop
对象并初始化, 如果您不知道使用哪个事件循环, 请使用这个函数返回的ev_loop
对象(或通过EV_DEFAULT宏).
如果ev_loop
已经被初始化, 那么再(多)次调用都只会简单返回同一个对象(即使flags
不同). 如果尚未初始化, 那么将会根据flags
创建它.
注意: 此函数不是线程安全的. 所以, 要在多线程使用的时候必须加上互斥锁保证操作的原子性(虽然这种情况极少). 同时”缺省“的ev_loop
对象才可以处理ev_child
.
示例:
1 | if (!ev_default_loop (0)) |
限制Libev
仅使用select
与poll
后端同时忽略环境配置的示例:
1 | ev_default_loop (EVBACKEND_POLL | EVBACKEND_SELECT | EVFLAG_NOENV); |
flags
参数的描述会在ev_loop_new
中会详细说明.
此函数创建并初始化一个新的事件循环对象.如果不能初始化循环, 则返回NULL
. 函数的调用是线程安全的. 通常我们会为每一个线程创建一个ev_loop
, 带在主线程中使用”缺省“的ev_loop
.
flags
参数可以用来指定要使用的特殊行为或特定后端, 而通常情况下可以被指定为0
(或EVFLAG_AUTO
宏);
以下是flags
支持的标志位:
EVFLAG_AUTO
默认标志位. 如果您不知道如何选择, 那么最好选择使用它.
EVFLAG_NOENV
默认情况下,
Libev
会在环境中寻找该环境变量并且覆盖其它特殊设置. 如果指定了此标志位,Libev
则不会再使用LIBEV_FLAGS
环境变量. 这个标志配置对于开发期间的性能测试、Bug
检查等配置尤为有用.
EVFLAG_FORKCHECK
通过设置此标志位让
Libev
在每次事件迭代中检查fork
; 通常使用的是getpid
来进行检查, 这可能会因为内核(系统)的不同对迭代速度有些许影响. 优点则是无需再为fork
检查担心.
注意: 此标志不能被
LIBEV_FLAGS
重写或指定.
EVFLAG_NOINOTIFY
当指定了此标志位,
ev_stat
不再尝试使用inotify
来进行检查. 启用inotify
则可以让ev_stat
保存inotify
的句柄(handle
), 这通常能减少内部消耗.
EVFLAG_SIGNALFD
当指定了此标志位,
Libev将
使用signalfd
的API
来优化ev_signal
实现信号处理. 这能串行化处理信号数据, 简化线程间的信号处理. 默认情况下signalfd
不会被使用, 因为这会改变你的信号掩码.
EVFLAG_NOSIGMASK
当指定了此标志位,
Libev
将避免修改信号掩码. 这意味着当你想接收信号时它们不会被阻塞.
当您希望自己处理信号或希望在特定的线程中处理信号, 它将变得非常有用.
EVFLAG_NOTIMERFD
当指定了此标志位,
Libev
将不会使用timerfd
来检查时间跳跃. 虽然Libev
仍能检查时间跳跃, 但是这会需要花费更多的时间.
当前会在第一个周期定时器创建的时候开始使用timerfd
, 如果因为各种原因失败, 则会退回到其它方法中完成.
EVBACKEND_SELECT
(value 1, portable select backend
)
使用标准的
select(2)
后端, 但是Libev
会尝试自己调整fd_set
以达到避免fds
数量限制. 如果失败, 那么使用select
后端对fd
的监控数量会非常低且它非常低效(O(highest_fd)
). 不过, 在监视少量文件描述符事件的后端中它通常是最快的.
此后端将
EV_READ
映射到readfds
结合上, 将EV_WRITE
映射到writefds
集合.
EVBACKEND_POLL
(value 2, poll backend
)
使用标准的
poll(2)
后端, 它的复杂度比select
更高, 但是能解决fd_set
的稀疏数组与fds
的文件描述符数量限制. 不过它在拥有大量不活跃fd
的时候事件通知效率毅然很低O(total_fds)
.
此后端将
EV_READ
映射为POLLIN | POLLERR | POLLHUP
, 将EV_WRITE
映射为POLLOUT | POLLERR | POLLHUP
.
EVBACKEND_EPOLL
(value 4, Linux
)
使用特定于
Linux
的epoll(7)
接口(适用于2.6
之后的内核). 对于很少的fds
比select
和poll
稍微慢一点, 但它的扩展性则会更好. 相较于前者的O(total_fds)
,epoll
则是O(active_fds)
.
而指的一提的是作为高级事件驱动接口存在错误的设计. 所幸, 这些都可以被内部额外的编程解决. (译者: 这里相当大一部分是作者对设计的吐槽, 有兴趣的同学自己阅读原文这里不再赘译.)
此后端映射
EV_READ
和EV_WRITE
的方式与EVBACKEND_POLL
相同.
EVBACKEND_LINUXAIO
(value 64, Linux
)
在
4.18
之后的内核中可以使用特定的Linux AIO
(不是aio
而是io_submit
)事件接口(但Libev
只会在4.19
中启动它). 如果这个后端可用, 那么可能值得使用它. 否则, 最好忽略回退选择使用epoll
较好.
Linux AIO
似乎并不是一个通用的后端, 所以epoll
会作为协助处理无法正常工作的文件描述符. 甚至在出现内核故障的时候, 直接退回到epoll
.(译者: 这里是意译.)
此后端映射
EV_READ
和EV_WRITE
的方式与EVBACKEND_POLL
相同.
EVBACKEND_KQUEUE
(value 8, most BSD clones
)
kqueue
其实特别值得一提, 因为在除NetBSD
之外的其它BSD
实现上都有问题(Break
). 然而与epoll
设计不同的是, 它的这些错误可以在不更改现有API
的情况下被修复. 所以, 除非您明确指定EVBACKEND_KQUEUE
否则不会被作为这些平台的首选.
kqueue
与epoll
可扩展性一样, 但是内核的接口更为高效(并不只是说速度). 并且此后端通常在大多数情况下都表现良好.
此后端将
EV_READ
映射到带有NOTE_EOF
的EVFILT_READ kevent
,并将EV_WRITE
映射到带有NOTE_EOF
的EVFILT_WRITE kevent
.
EVBACKEND_DEVPOLL
(value 16, Solaris 8
)
尚未实现.
EVBACKEND_PORT
(value 32, Solaris 10
)
Solaris 10
的事件接口. 虽然它也很慢, 但是仍然能保证O(active_fds)
的效率. 它在每次循环迭代中,每个活动文件描述符都需要一个系统调用. 所以在少量fds
中选择使用select
和poll
通常会更好.
值得一提的是: 他们(
Sun
)给出的代码示例都是错的, 但是所幸的是Libev
能够解决这些白痴问题(work around these idiocies
)
此后端映射
EV_READ
和EV_WRITE
的方式与EVBACKEND_POLL
相同.
如果上述一个或多个后端标志被添加到标志值中,那么只有这些后端会被尝试(以相反的顺序). 如果没有指定, 那将尝试 ev_recommended_backends()
中的所有后端。
这个示例尝试创建一个仅使用epoll
的事件循环:
1 | struct ev_loop *epoller = ev_loop_new (EVBACKEND_EPOLL | EVFLAG_NOENV); |
这个示例假设但如果可用, 请确保使用kqueue
:
1 | struct ev_loop *loop = ev_loop_new (ev_recommended_backends () | EVBACKEND_KQUEUE); |
同样的, 如果可以则希望使用Linux AIO
. 否则, 使用其它后端:
1 | struct ev_loop *loop = ev_loop_new (ev_recommended_backends () | EVBACKEND_LINUXAIO); |
如果是”缺省“的loop
返回true
, 否则返回false
.
返回当前loop
的迭代次数.
设置与获取loop
的用户自定义对象(void* data
), 这通常用来让loop
携带一些特殊的对象(上下文).
这个函数根据EV_VERIFY
宏在内部做一些健壮性、可靠性的检查与验证, 如果发现错误会立即抛出错误消息并调用abort()
.
这通常在开发、调试期间尤为有用, 有利于协助我们排查问题. 而在生产环境中最好避免使用, 过多的检查会影响整体性能.
可以用来调用ev_run
提前返回(但必须在处理完所有未处理的事件之后).
how
为EVBREAK_ONE
会返回一层ev_run
嵌套.
how
为EVBREAK_ALL
会返回所有ev_run
嵌套.
ev_run
返回之后再次调用ev_run
则会清除break state
.
ev_run
(外部)调用ev_break
不会产生任何影响.
返回正在使用的后端标志位.
此函数通常在初始化完成所有的观察者并且想开始处理事件之后被调用. 它将向操作系统询问任何新事件<->
调用观察者回调, 然后无限期地重复这个过程.
如果flags
参数为0
,它将在内部持续处理事件,直到不再有事件处于活动状态或主动调用ev_break
. 如果没有更多活动的观察者, 那么此函数将会返回. 请注意, 显示调用ev_break
来停止事件循环通常是最好的方式.
EVRUN_ONCE
查找并处理任何已触发(
pending
)但未完成的事件. 在至少处理了1
个事件后ev_run
将会直接返回.
EVRUN_NOWAIT
查找并检查所有事件, 在经过一次迭代后如果没有已触发(
pending
)但未完成的事件则ev_run
返回.
下面则是ev_run
内部大致的运行流程(不保证将来不会改变):
1 | - 递增`loop`深度. |
1 | // 只需导入单个头文件 |