基于C++标准库实现定时器类
定时器类是多线程编程中经常设计到的工具类
简单的定时器原理其实很简单(是不是有点GNU is not unix的味道;):
- 创建一个新线程
- 在那个线程里等待
- 等待指定时长后做任务
python标准库中就有这么一个定时器类:threading.Timer
此类表示一个操作应该在等待一定的时间之后运行 --- 相当于一个定时器。
Timer 类是 Thread 类的子类,因此可以像一个自定义线程一样工作。
与线程一样,通过调用 start() 方法启动定时器。 而 cancel() 方法可以停止计时器(在计时结束前)。
例如:
def hello():
print("hello, world")
t = Timer(30.0, hello)
t.start() # after 30 seconds, "hello, world" will be printed
class threading.Timer(interval, function, args=None, kwargs=None) 创建一个定时器,在经过 interval 秒的间隔事件后,将会用参数 args 和关键字参数 kwargs 调用 function。
如果 args 为 None (默认值),则会使用一个空列表。如果 kwargs 为 None (默认值),则会使用一个空字典。
start() 启动定时器。
cancel() 停止定时器并取消执行计时器将要执行的操作。仅当计时器仍处于等待状态时有效。
接下来我将给出两种C++的Timer实现,接口类似于python的threading.Timer,不过精度为秒级的,其原因有二:
- 实现代码参考了Posix多线程编程里的alarm实例程序,为了方便大家对比C语言版本的实现,这里也以秒为单位
- 避免一上来过多的涉及C++标准库中
<chrono>
的用法,使代码逻辑更清晰的集中在定时器相关的部分
当然,作为一个在产品级代码中可用的Timer,精度至少应该在毫秒级才行,所以文章最后也会给出精度在微秒的代码实现的链接。
首先,给出C++版本的Timer的接口定义:
几乎完全仿照python的threading.Timer,
class Timer {
public:
typedef std::function<void ()> Callback;
Timer(int interval, Callback function);
void start();
void cancel();
};
- Callback:类型为std::function<void ()>,即返回类型为void的“函数”,当然在C++里可以是普通函数,函数对象,lambda等。
- Timer(interval, function):构造函数,创建一个Timer,interval秒后到期(相对于调用start函数时的时间点),回调函数为function。
- start():启动定时器。
- cancel():停止定时器并取消执行计时器将要执行的操作。
在给出C++的实现前,我们先给出测试驱动程序。测试驱动程序来源于《Posix多线程程序设计》(英文原版书名为Programming with POSIX Threads)里的闹钟实例程序。
而我接下来的介绍顺序源于我的编码实现顺序,如下:
- 先给出书中基于pthread的多线程版本的实例代码,C代码
- 将C版本的代码转化成等价的python代码,基于threading.Timer接口实现的版本
- 将python版本的代码,转化成C++版本,并基于C++的Timer类接口,得到C++的Timer类的测试驱动代码
- 实现C++版的Timer类,并且编译测试驱动代码,运行验证
那么,我先给出基于pthread的多线程版本的实例代码,C代码:
/*
* alarm_fork.c
*
* This version of alarm.c uses pthread_create to create a
* separate thread to wait for each alarm to expire.
*/
#include <pthread.h>
#include "errors.h"
typedef struct alarm_tag {
int seconds;
char message[64];
} alarm_t;
void *alarm_thread (void *arg)
{
alarm_t *alarm = (alarm_t*)arg;
int status;
status = pthread_detach (pthread_self ());
if (status != 0)
err_abort (status, "Detach thread");
sleep (alarm->seconds);
printf ("(%d) %s\n", alarm->seconds, alarm->message);
free (alarm);
return NULL;
}
int main (int argc, char *argv[])
{
int status;
char line[128];
alarm_t *alarm;
pthread_t thread;
while (1) {
printf ("Alarm> ");
if (fgets (line, sizeof (line), stdin) == NULL) exit (0);
if (strlen (line) <= 1) continue;
alarm = (alarm_t*)malloc (sizeof (alarm_t));
if (alarm == NULL)
errno_abort ("Allocate alarm");
/*
* Parse input line into seconds (%d) and a message
* (%64[^\n]), consisting of up to 64 characters
* separated from the seconds by whitespace.
*/
if (sscanf (line, "%d %64[^\n]",
&alarm->seconds, alarm->message) < 2) {
fprintf (stderr, "Bad command\n");
free (alarm);
} else {
status = pthread_create (
&thread, NULL, alarm_thread, alarm);
if (status != 0)
err_abort (status, "Create alarm thread");
}
}
}
代码的完整说明参见《Posix多线程程序设计》 1.5.3章节,这里就不再搬运原文了。
接下来是移植成python版本的代码:
#!/usr/bin/env python3
from threading import Timer
class Alarm:
def __init__(self, seconds:int, message:str):
self.seconds = seconds
self.message = message
def callback(alarm:Alarm):
print("({}) {}\n".format(alarm.seconds, alarm.message))
if __name__ == "__main__":
while True:
line = input("Alarm> ")
if len(line) <= 1:
continue
try:
seconds, *message = line.split(' ')
alarm = Alarm(int(seconds), ' '.join(message))
t = Timer(interval=int(seconds), function=callback, args=(alarm, ))
t.start()
except:
print("Bad command")
python版本的代码大家有兴趣的可以在本地运行一下,看看效果;)
再然后,我把这段代码翻译成C++版本的,基于C++的Timer类接口:
cpp
#include "timer.hpp"
#include <cstdlib>
#include <string>
#include <memory>
#include <iostream>
struct Alarm {
Alarm(int seconds_, const std::string& message_):
seconds(seconds_), message(message_) {
}
int seconds;
std::string message;
};
void callback(std::shared_ptr<Alarm> alarm) {
std::cout << "(" << alarm->seconds << ") " << alarm->message << std::endl;
}
std::tuple<int, std::string> parse_command(const std::string& line) {
auto pos = line.find(' ');
if (pos == std::string::npos)
throw std::runtime_error("invalid line: separator not found");
int seconds = std::stoi(line.substr(0, pos));
std::string message = line.substr(pos+1);
return std::make_tuple(seconds, message);
}
int main()
{
std::string line;
int seconds;
std::string message;
while (true) {
std::cout << "Alarm> ";