我有一个定期运行的计时器。我使用timer_create()使用SIGEV_THREAD选项创建计时器。这将在计时器到期时在线程上触发回调,而不是向进程发送SIGALRM信号。问题是,每当我的计时器到期时,就会产生一个新的线程。这意味着根据定时器的频率,程序可能会产生数百个线程。最好是让一个线程来处理回调。当对信号使用timer_create()时(通过使用sigaction),我可以做到这一点,但不仅仅是线程。有没有什么方法可以不使用信号,但仍然让计时器在单个现有线程中通知进程?或者我应该从性能的角度(线程与信号)来担心这一点吗?
编辑:
我的解决方案是使用SIGEV_SIGNAL和pthread_sigmask()。因此,我继续依靠信号来知道我的计时器何时到期,但我可以100%确定只有一个线程(由我创建)被用来捕获信号并执行适当的操作。
tl;dr:SIGEV_THREAD
不能基于信号工作的基本前提是错误的——信号是产生新线程的底层机制。glibc不支持为多个回调重用同一个线程。
timer_create
的行为与您想象的不完全一样——它的第二个参数struct sigevent *restrict sevp
包含字段sigevent_notify
,该字段具有以下文档:
SIGEV_THREAD
通过调用sigev_Notify_function来通知进程;像如果";它是一个新线程的启动函数。(在这里的实现可能性是每个定时器通知可能导致创建一个新线程,或者单个线程创建以接收所有通知)函数被调用sigev_value作为其唯一参数。如果sigev_notify_attributes为不是NULL,它应该指向pthread_attr_t结构,该结构定义属性(请参见pthread_attr_init(3))。
事实上,如果我们看看glibc的实现:
else
{
/* Create the helper thread. */
pthread_once (&__helper_once, __start_helper_thread);
...
struct sigevent sev =
{ .sigev_value.sival_ptr = newp,
.sigev_signo = SIGTIMER,
.sigev_notify = SIGEV_SIGNAL | SIGEV_THREAD_ID,
._sigev_un = { ._pad = { [0] = __helper_tid } } };
/* Create the timer. */
INTERNAL_SYSCALL_DECL (err);
int res;
res = INTERNAL_SYSCALL (timer_create, err, 3,
syscall_clockid, &sev, &newp->ktimerid);
我们可以看到__start_helper_thread
的实现:
void
attribute_hidden
__start_helper_thread (void)
{
...
int res = pthread_create (&th, &attr, timer_helper_thread, NULL);
接着是timer_helper_thread
的实现:
static void *
timer_helper_thread (void *arg)
{
...
/* Endless loop of waiting for signals. The loop is only ended when
the thread is canceled. */
while (1)
{
...
int result = SYSCALL_CANCEL (rt_sigtimedwait, &ss, &si, NULL, _NSIG / 8);
if (result > 0)
{
if (si.si_code == SI_TIMER)
{
struct timer *tk = (struct timer *) si.si_ptr;
...
(void) pthread_create (&th, &tk->attr,
timer_sigev_thread, td);
因此,至少在glibc级别上,当使用SIGEV_THREAD
时,您必须使用信号来向线程发出创建函数的信号,而且似乎您一开始的主要动机是避免使用警报信号。
在Linux源代码级别,计时器似乎只处理信号——kernel/time/posix_timers.c
函数中的posix_timer_event
(由kernel/time/alarmtimer.c
中的alarm_handle_timer
调用)直接进入signal.c
中必须发送信号的代码。因此,在使用timer_create
时,似乎不可能避免信号,而您的问题中的这句话——";这将在计时器到期时在线程上触发回调,而不是向进程发送SIGALRM信号"-为假(尽管信号不必是SIGALRM
是真的)。
换句话说,与信号相比,SIGEV_THREAD
似乎没有任何性能优势。信号仍将用于触发线程的创建,并且您添加了创建新线程的额外开销。
有没有办法不使用信号,但仍然有计时器通知单个现有进程线
有:timerfd API(timerfd_create()
)提供了这样的手段。
总的来说,如果你能使用timerfd_create()
(可移植性不是你关心的问题),那么你应该这样做。因为SIGEV_THREAD已经完全崩溃:它可以生成如果处理程序不是足够快,正因为如此,你甚至无法使用CCD_ 18可靠。
CCD_ 19也不是解决方案,虽然是不可移植的,但你也不容易获得没有多少libc实现它的tidCCD_ 20从CCD_。
因此,如果必须使用timer_create()
,那么CCD_ 23+CCD_;有点";作品至少你可以使用timer_getoverrun()
那里,但它仍然非常奇怪:timer_getoverrun()
指定用于提供额外超支金额这种情况发生在信号排队和排队之间。但如果你问之后发生的超支情况如何信号出列,但在您的程序调用之前timer_getoverrun()
,那么你将找不到任何答案除非您查看linux源代码。从那里可以看出,linux实际上测量了在后续信号dequeuing之间溢出,因此这可能是将损失的超支添加到下一次信号传递。另外,正如你已经提到的,将定时器信号引导到特定线程并不总是可靠的:一些不知情的lib可能会创建线程并更改sigmask以将其与自己的线程一起使用计时器,但会窃取计时器的信号。等等。
linux提供了遗留的免费API,这很好像timerfd_create()
,但当你想写可移植代码,你很快就会发现自己工作解决方案。