启动一个线程使主线程超时,但允许主线程暂停定时器。

问题描述 投票:1回答:1

我有一个C函数 expensive_call 我想在其中添加一个 "超时"。为此,我使用了pthreads。我创建了一个单独的线程,它调用 nanosleep 并在之后发送一个信号(SIGUSR1)到主线程。

但是,主线程是可以将某些代码标记为不计入超时的。所以我想到了主线程可以向主线程发送一个信号(SIGUSR2)到定时器线程中去暂停、恢复定时器。

我使用 sigsetjmpsiglongjmp 当主线程接收到 SIGUSR1. 信号处理程序,用于 SIGUSR2 是空的。

我现在的实现存在以下两个问题。

  1. 有时 SIGUSR2 已收到,但 nanosleep 不停止,并且 expensive_call 反正是被打断了。(为此,我试着添加了 sched_yield(); 正上方 for (;;);expensive_call,让定时器线程接管,但这没有任何效果)。)
  2. 这个解决方案需要同时使用 SIGUSR1SIGUSR2,我想我不需要用这两个来做。

任何解决这些问题的想法都将受到欢迎!

下面程序的预期输出是。

[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);
}
c multithreading pthreads
1个回答
0
投票

我不明白为什么你不使用semaphores来实现一个概念。

旗语是线程间信号传递的一种常用且最简单的方式。但它需要你为semaphore编码检查点来触发函数。

编辑:分解一下,一个semaphore可以看成是一个全局变量(或者至少是在参与线程的代码范围内)。虽然真正的目的是发出信号,表明某个部分已经准备好让其他线程来处理,但你可以只钢化其中的一小部分:全局变量。

根据我的理解,你的方法是试图实现一种软件看门狗。看门狗基本上会在用完特定的时间后重置,以检测软件是否存在未定义状态或死锁。因此,我会选择简单的解决方案,设置一个全局变量,其中包含你所接受的时间(带有一些安全缓冲区),让定时器线程进行倒计时。如果它达到0,则杀死主线程。

如果要暂停或者干脆跳过那些不在你控制范围内但值得信任的昂贵的调用,我会把时间设置到最大,然后终止该线程。要在昂贵的调用返回后恢复重新创建。


0
投票

我按照sbo的思路实现了一个解决方案。建议 使用一个mutex和一个条件变量。条件变量用于两个方向的信号传递。

  1. 主线程启动定时器线程并等待条件变量。这样定时器线程就可以启动了,解决了我的第一个问题。

  2. 定时器线程向条件变量发出信号,并做一个 pthread_cond_timedwait() 来最多等待到超时。

  3. 如果主线程及时完成,它就会取消定时器线程。否则,定时器线程会在当 ETIMEDOUT 发生。

  4. 要允许在 expensive_call() 的,不计入超时,主线程在进入和退出这样的部分之前和之后,对条件变量发出信号。定时器线程在收到该信号后,计算剩余的超时时间,并在主线程退出未计算的部分后,继续进行步骤2。

#include <errno.h>
#include <pthread.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>

static pthread_t main_thread,timer_thread;
static pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cv;

static void *timer (void *arg)
{
    struct timespec start_time,wait_time,end_time;
    int _unused,err;

    pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS,&_unused);

    wait_time.tv_sec=1;
    wait_time.tv_nsec=0;

    clock_gettime (CLOCK_MONOTONIC,&start_time);
    end_time.tv_sec=start_time.tv_sec+wait_time.tv_sec;
    end_time.tv_nsec=start_time.tv_nsec+wait_time.tv_nsec;

    for (;;)
    {
        pthread_cond_signal (&cv);
        err=pthread_cond_timedwait (&cv,&mutex,&end_time);

        if (!err){
            clock_gettime (CLOCK_MONOTONIC,&end_time);
            wait_time.tv_sec=wait_time.tv_sec-(end_time.tv_sec-start_time.tv_sec);
            wait_time.tv_nsec=wait_time.tv_nsec-(end_time.tv_nsec-start_time.tv_nsec);

            pthread_cond_signal (&cv);
            pthread_cond_wait (&cv,&mutex);

            clock_gettime (CLOCK_MONOTONIC,&start_time);
            end_time.tv_sec=start_time.tv_sec+wait_time.tv_sec;
            end_time.tv_nsec=start_time.tv_nsec+wait_time.tv_nsec;
            continue;
        }

        if (err==ETIMEDOUT){
            fprintf (stderr,"killing main thread...\n");
            pthread_mutex_unlock (&mutex);
            pthread_kill (main_thread,SIGUSR1);
            break;
        } else {
            perror ("pthread_mutex_timedlock");
        }
    }

    return NULL;
}

static void enter_uncounted_section (void)
{
    pthread_cond_signal (&cv);
    pthread_cond_wait (&cv,&mutex);
    pthread_mutex_unlock (&mutex);
}

static void exit_uncounted_section (void)
{
    pthread_cond_signal (&cv);
    pthread_cond_wait (&cv,&mutex);
    pthread_mutex_unlock (&mutex);
}

static void expensive_call (void)
{
    fprintf (stderr,"start expensive call\n");

    enter_uncounted_section();
    for (long long int i=0; i<1000000000L; i++);
    exit_uncounted_section();

    fprintf (stderr,"exited uncounted section\n");

    for (long long int i=0; i<1000000000L; i++);

    fprintf (stderr,"end expensive call\n");
}

static jmp_buf restore_point;

static void handle_sigusr1 (int sig)
{
    fprintf (stderr,"received SIGUSR1\n");
    siglongjmp (restore_point,sig);
}

void main (void)
{
    struct sigaction signal_handler;
    pthread_condattr_t attr;

    /* 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");

    /* Setup threads */
    pthread_condattr_init (&attr);
    pthread_condattr_setclock (&attr,CLOCK_MONOTONIC);
    pthread_cond_init (&cv,&attr);

    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 {
        pthread_cond_wait (&cv,&mutex);
        pthread_mutex_unlock (&mutex);
        expensive_call();
    }

    /* Cleanup */
    pthread_cancel (timer_thread);
    pthread_join (timer_thread,NULL);
    pthread_cond_destroy (&cv);
    pthread_mutex_destroy (&mutex);
}
© www.soinside.com 2019 - 2024. All rights reserved.