C++高并发异步定时器的实现

在C++高并发场景,定时功能的实现有三大难题:高效、精准、原子性。

除了定时任务随时可能到期、而进程随时可能要退出之外,最近Workflow甚至为定时任务增加了取消功能,导致任务可能被框架调起之前被用户取消,或者创建之后不想执行直接删除等情况,而这些情况大部分来说都是由不同线程执行的,因此其中的并发处理可谓教科书级别! 那么就和大家一起看看Workflow在定时器的设计上做了哪些考虑,深扒细节,体验并发架构之美~

github.com/sogou/workf…

1. 高效的数据结构与timerfd

举个例子:实现一个server,收到请求之后,隔1s再回复给用户。

聪明的读者肯定知道,在server的执行函数中用**sleep(1)**是不行的,sleep()这个系统调用是会阻塞当前线程的,而异步编程里阻塞线程是高效的大忌!

所以我们可以使用timerfd,顾名思义就是用特定的fd来通知定时事件,把定时事件响应和网络事件响应都一起处理,用epoll管理就是一把梭。

现在离高效还差一点。回到例子,我们不可能每次收到一个请求都创建一个timerfd,因为高并发场景下一个server通常要抗上百万的QPS。

目前Workflow的超时算法做法是:一个poller有一个timerfd,内部利用了链表+红黑树的数据结构,时间复杂度在O(1)和O(logn)之间,其中n为poller线程的fd数量。

2. 精准的响应

这样的数据结构设计有什么好处呢?

  • 写得快(放入一个新节点)

  • 读得快(响应已超时的节点)

  • 精度高(超时时间无精度损失)

Workflow源码在kernel和factory目录中都有对应的实现,kernel层是主要负责timerfd的地方,当前factory层还比较薄。我们重点看看上述数据结构。

写:由用户发起异步任务,将这个任务加到上述的链表+红黑树的数据结构中,如果这个超时是当前最小的超时时间,还会更新一下timerfd。

读:框架的网络线程每次会从epoll拿出事件,如果响应到超时事件,会把数据结构中已经超时的全部节点都拿出来,并调用任务的handle。

以下是从epoll处理超时事件的关键函数:

/*** poller响应timerfd的到时事件,并处理所有到时的定时任务 ***/
static void __poller_handle_timeout(const struct __poller_node *time_node, poller_t *poller)                           
{                                                                               
    ...

    // 锁里,把list与rbtree上时间已到的节点都从数据结构里删除,临时放到一个局部变量上                                        
    list_for_each_safe(pos, tmp, &poller->timeo_list)                           
    {
       ...
       node->removed = 1; // 标志位:【removed】
       ...
    }

    if (poller->tree_first)                                                     
    { ... }  

    // 锁外,设置state和error,并回调Task的handle()函数
    while (!list_empty(&timeo_list
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值