在C++线程内实现多个计时器的最安全方法

Safest way to implement multiple timers inside a thread in C++

本文关键字:计时器 安全 方法 C++ 线程 实现      更新时间:2023-10-16

正如标题所说,我正在寻找在C++(不是c ++ 11(中实现多个计时器的最佳方法。 我的想法是有一个线程(posix(来处理计时器。 我至少需要 4 个计时器、3 个周期性和 1 个单次。 最小分辨率应为 1 秒(对于最短计时器(,对于最长计时器,最小分辨率应为 15 小时。 所有计时器应同时运行。

这些是我想到的不同实现(我不知道它们是线程环境中最安全的还是最简单的(:

1(使用itimerspec,sigaction和sigevent结构,如下所示:

static int Tcreate( char *name, timer_t *timerID, int expireMS, int intervalMS )
{
struct sigevent         te;
struct itimerspec       its;
struct sigaction        sa;
int                     sigNo = SIGRTMIN;
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = app;
sigemptyset(&sa.sa_mask);
if (sigaction(sigNo, &sa, NULL) == -1)
{
perror("sigaction");
}
/* Set and enable alarm */
te.sigev_notify = SIGEV_SIGNAL;
te.sigev_signo = sigNo;
te.sigev_value.sival_ptr = timerID;
timer_create(CLOCK_REALTIME, &te, timerID);
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = intervalMS * 1000000;
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = expireMS * 1000000;
timer_settime(*timerID, 0, &its, NULL);
return 1;
}

2( 使用 clock(( 并检查时差,如下所示:

std::clock_t start;
double duration;
start = std::clock();
duration = ( std::clock() - start ) / (double) CLOCKS_PER_SEC;

3(像这样使用时间:

auto diff = tp - chrono::system_clock::time_point();
cout << "diff:" << chrono::duration_cast<chrono::minutes>(diff).count()
<< " minute(s)" << endl;
Days days = chrono::duration_cast<Days>(diff);
cout << "diff:" << days.count() << " day(s)" << endl;

请将这些视为想法,而不是实际的工作代码。

你对此有何看法?

如果您的计时器线程负责计时器,并且最小分辨率为 1 秒,并且计时不需要那么精确(即如果 +/- 0.1 秒足够好(,那么计时器线程的简单实现是只休眠 1 秒,检查是否有任何需要触发的计时器, 并重复,如以下伪代码所示:

repeat:
sleep 1
t = t+1
for timer in timers where timer(t) = true:
fire(timer)

困难的部分是填充存储计时器的结构 - 大概计时器将由其他线程设置,可能由多个线程设置,这些线程可以尝试同时设置计时器。 建议使用一些标准数据结构(如线程安全队列(将消息传递给计时器线程,然后在每个周期更新计时器集合本身:

repeat:
sleep 1
t = t+1
while new_timer_spec = pop(timer_queue):
add_timer(new_timer_spec)
for timer in timers where timer(t) = true:
fire(timer)

要考虑的另一件事是fire(timer)的性质 - 这里要做什么实际上取决于使用计时器的线程的需求。 也许只需设置一个他们可以读取的变量就足够了,或者这可以触发线程可以侦听的信号。

由于您所有的计时器创建显然都通过单个 API(即,控制代码可以看到所有计时器(,因此您可以完全避免信号或繁忙循环,并保留一个排序的计时器列表(如按截止日期键入的std::map(,只需使用 (例如(pthread_cond_timedwait等待条件变量。条件变量互斥锁保护计时器列表。

如果您安排了截止时间早于当前"下一个"计时器的新计时器,则需要唤醒睡眠线程并安排调整后的睡眠(如果不是因为此要求,您可以使用普通usleep或其他方式(。这一切都发生在与条件变量关联的互斥锁中。

您不必使用条件变量,但它们似乎是最干净的,因为关联的互斥锁自然用于保护计时器列表。您可能也可以在带有sem_timedwait的 semaphone 之上构建它,但在内部插座、管道或类似的东西上构建select,但随后您将单独控制对计时器队列的多线程访问。