【发布时间】:2020-05-18 15:20:24
【问题描述】:
我有一个 C 函数 expensive_call,我想在其上添加一个“超时”。为此,我使用 pthreads:我创建了一个单独的线程,它调用 nanosleep,然后向主线程发送一个信号 (SIGUSR1)。
但是,允许主线程将某些代码片段标记为不计入超时。所以我想到了主线程可以向定时器线程发送信号(SIGUSR2)来暂停/恢复定时器。
当主线程收到SIGUSR1时,我使用sigsetjmp/siglongjmp从昂贵的调用中返回。 SIGUSR2 的信号处理程序为空。
下面我当前的实现有两个问题:
- 有时
SIGUSR2被接收,但nanosleep没有停止,expensive_call无论如何都会被中断。 (为此,我尝试在expensive_call中的for (;;);上方添加sched_yield();,以允许计时器线程接管,但这没有任何效果。) - 此解决方案需要
SIGUSR1和SIGUSR2,我想我不必同时使用这两个。
欢迎任何解决这些问题的想法!
下面程序的预期输出是:
[main thread] start expensive call
[timer thread] received SIGUSR2
[timer thread] pausing timer
(does not terminate)
但有时我们会得到(这是上面的问题1):
[main thread] start expensive call
[timer thread] received SIGUSR2
[timer thread] killing main thread...
[main thread] received SIGUSR1
[main thread] expensive_call() was interrupted
程序本身:
#include <errno.h>
#include <pthread.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
static pthread_t main_thread,timer_thread;
static jmp_buf restore_point;
static void handle_sigusr1 (int sig)
{
fprintf (stderr,"received SIGUSR1\n");
siglongjmp (restore_point,sig);
}
static void handle_sigusr2 (int sig)
{
fprintf (stderr,"received SIGUSR2\n");
}
static void *timer (void *arg)
{
struct timespec timeout;
sigset_t sigset;
int _unused;
pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS,&_unused);
/* We ignore everything except SIGUSR2, which is used in sigwait below */
sigfillset (&sigset);
sigdelset (&sigset,SIGUSR2);
pthread_sigmask (SIG_SETMASK,&sigset,NULL);
sigemptyset (&sigset);
sigaddset (&sigset,SIGUSR2);
timeout.tv_sec=1;
timeout.tv_nsec=0;
/* On interrupt, wait for SIGUSR2, then continue to sleep */
while (nanosleep (&timeout,&timeout) == -1 && errno==EINTR){
fprintf (stderr,"pausing timer\n");
sigwait (&sigset,&_unused);
fprintf (stderr,"continuing timer\n");
}
fprintf (stderr,"killing main thread...\n");
pthread_kill (main_thread,SIGUSR1);
return NULL;
}
static void expensive_call (void)
{
fprintf (stderr,"start expensive call\n");
pthread_kill (timer_thread,SIGUSR2);
for (;;);
pthread_kill (timer_thread,SIGUSR2);
fprintf (stderr,"end expensive call\n");
}
void main (void)
{
struct sigaction signal_handler;
/* Install signal handlers */
signal_handler.sa_handler=handle_sigusr1;
sigemptyset(&signal_handler.sa_mask);
signal_handler.sa_flags=SA_RESTART;
if (sigaction (SIGUSR1,&signal_handler,NULL)!=0)
perror ("sigaction");
signal_handler.sa_handler=handle_sigusr2;
if (sigaction (SIGUSR2,&signal_handler,NULL)!=0)
perror ("sigaction");
/* Setup threads */
main_thread=pthread_self();
pthread_create (&timer_thread,NULL,timer,NULL);
/* Actual computation */
if (sigsetjmp (restore_point,1)!=0){
fprintf (stderr,"expensive_call() was interrupted\n");
} else {
expensive_call();
}
/* Cleanup */
pthread_cancel (timer_thread);
pthread_join (timer_thread,NULL);
}
【问题讨论】:
-
也许不使用线程和信号,只是让
expensive_call()在其执行开始时存储当前挂钟时间,然后定期检查当前挂钟时间与存储挂钟时间,如果差值大于指定阈值,则返回。 (您可以通过将在“暂停部分”内花费的时间添加到存储的挂钟时间来实现expensive_call()内的“暂停”) -
@JeremyFriesner 我对
expensive_call()没有太多控制权。这是一个解释器循环,所以我可以在主循环中进行检查,但这会很慢,因为一次迭代非常快。只有部分实现的指令应该能够暂停计时器。 -
如果您担心过多计时器检查的开销,您总是可以做一些事情来降低频率,例如
static int counter = 0; if (++counter == 100000) {counter = 0; check_timer();} -
@JeremyFriesner 是的,但即使是柜台的开销也会很明显。
-
我对此表示怀疑,但这很容易测量和查看。
标签: c multithreading pthreads