如果说是在linux服务器下,提供两种编码比较简单的常见方案,抛砖引玉:
一种是使用内核提供的定时器功能,定时精度可达纳秒级(当然普通硬件的纳秒级几乎不可靠):
通过timerfd_create(2)函数创建一个定时器描述符,在flags参数里设置非阻塞和exec后关闭,然后设置好定时间隔和是否只执行一次之后,把描述符加入到epoll的事件驱动系统里,当epoll_wait返回该描述符可读时,说明定时器到点,可以执行回调任务函数了。
另一种类似的常见定时器实现是,通过select/epoll_wait函数的超时功能,同时定义定时任务节点,用一个最小单位间隔时间设置timeval结构体,传入事件驱动主循环中select/epoll_wait的阻塞时间参数。这种实现方法的关键是要设计好一个管理定时任务节点的数据结构,尽可能地减少执行定时任务和增删定时任务的时间复杂度,保证最快到点的时间最先弹出数据结构容器,执行任务回调函数。常见的数据结构容器有,按到点时间排序的有序链表,最小时间堆等。这种方式精度为微秒级。
这两种方案的共同优点是,定时任务处理和普通网络socket读写事件处理都能使用同一个I/O多路复用机制,提升服务器负载能力,避免了另开线程。严重不推荐通过alarm函数或sleep函数来实现的定时的功能。原因很简单:一,定时精度不够;二,线程大部分时间处于睡觉状态,程序性能,cpu使用率低下。
那么一般游戏服务器里面的定时器都有哪些特点呢?
- 对时间的表达能力强,CronTab表达式已经在Linux平台上广泛使用,需求久经考验
- 使用方便,一个头文件搞定一切,拷贝过去就可以使用,不依赖第三方库,Windows、Centos、Ubuntu、Mac都可以运行。一行代码添加一个定时器,可传入成员函数,携带自定义参数,可以使用Cron表达式定点触发,也可以添加延迟触发。
- 精度高、误差不累积性能好,对于定时器内的对象个数,时间判断的时间复杂度做到了O(log(n))
底层提供的是我推荐的, 我也是这样做的, 一般可能是一个独立的计时线程, C++的话可以用操作系统提供的功能, 比如asio的timer. golang的话, 就会用sleep+goroutine的方式, 简单.
一般情况下, 我是把timer线程的信号当做一个消息post给工作线程来处理的, 这样比较简单. 也不用加锁
用主循环检测时间戳这种... 这是客户端转过来写服务器的写法, 要写也要开线程. 以前这种写法还能收消息呢, 不推荐
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.