使用 SIGEV_THREAD 时如何为所有 timer_create 回调重用一个线程?
How can I reuse a single thread for all timer_create callbacks when using SIGEV_THREAD?
我有一个定时运行的计时器。我使用 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
,它具有以下 documentation:
SIGEV_THREAD
Notify the process by invoking sigev_notify_function "as
if" it were the start function of a new thread. (Among the
implementation possibilities here are that each timer notification
could result in the creation of a new thread, or that a single thread
is created to receive all notifications.) The function is invoked
with sigev_value as its sole argument. If sigev_notify_attributes is
not NULL, it should point to a pthread_attr_t structure that defines
attributes for the new thread (see pthread_attr_init(3)).
确实,如果我们看一下 glibc 的 implementation:
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
的 implementation:
void
attribute_hidden
__start_helper_thread (void)
{
...
int res = pthread_create (&th, &attr, timer_helper_thread, NULL);
然后跟随 timer_helper_thread
的 implementation:
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
(由 alarm_handle_timer
在 [=23= 中调用) ]) 直接转到 signal.c
中必须发送信号的代码。因此,在使用 timer_create
时似乎无法避免信号,而您的问题中的这个陈述 - “这将在计时器到期时在线程上触发回调,而不是向进程发送 SIGALRM 信号。 “ - 是假的(尽管信号不一定是 SIGALRM
是真的)。
换句话说 - 与信号相反,SIGEV_THREAD
似乎没有性能优势。信号仍将用于触发线程的创建,并且您正在添加创建新线程的额外开销。
我有一个定时运行的计时器。我使用 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
,它具有以下 documentation:
SIGEV_THREAD
Notify the process by invoking sigev_notify_function "as if" it were the start function of a new thread. (Among the implementation possibilities here are that each timer notification could result in the creation of a new thread, or that a single thread is created to receive all notifications.) The function is invoked with sigev_value as its sole argument. If sigev_notify_attributes is not NULL, it should point to a pthread_attr_t structure that defines attributes for the new thread (see pthread_attr_init(3)).
确实,如果我们看一下 glibc 的 implementation:
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
的 implementation:
void
attribute_hidden
__start_helper_thread (void)
{
...
int res = pthread_create (&th, &attr, timer_helper_thread, NULL);
然后跟随 timer_helper_thread
的 implementation:
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
(由 alarm_handle_timer
在 [=23= 中调用) ]) 直接转到 signal.c
中必须发送信号的代码。因此,在使用 timer_create
时似乎无法避免信号,而您的问题中的这个陈述 - “这将在计时器到期时在线程上触发回调,而不是向进程发送 SIGALRM 信号。 “ - 是假的(尽管信号不一定是 SIGALRM
是真的)。
换句话说 - 与信号相反,SIGEV_THREAD
似乎没有性能优势。信号仍将用于触发线程的创建,并且您正在添加创建新线程的额外开销。