如何通过posix信号正确挂起多个线程?

问题描述:

在现有多线程应用程序的上下文中,我想暂停特定持续时间的线程列表,然后恢复其正常执行。我知道你们中有些人会说我不应该这样做,但我知道这一点,我没有选择。如何通过posix信号正确挂起多个线程?

我想出了下面的代码,那种工作,但随机失败。对于每个我想暂停的线程,我都会发送一个信号,并通过信号灯等待一个确认。信号处理程序在调用时发布信号并在指定的持续时间内休眠。

问题是当系统完全加载时,对sem_timedwait的调用有时会因为ETIMEDOUT而失败,而且我留下了一个不一致的逻辑,信号用于ack:我不知道信号是否已被丢弃或是只是晚了。

// compiled with: gcc main.c -o test -pthread 

#include <pthread.h> 
#include <stdio.h> 
#include <signal.h> 
#include <errno.h> 
#include <string.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <semaphore.h> 
#include <sys/types.h> 
#include <sys/syscall.h> 

#define NUMTHREADS 40 
#define SUSPEND_SIG (SIGRTMIN+1) 
#define SUSPEND_DURATION 80 // in ms 

static sem_t sem; 

void checkResults(const char *msg, int rc) { 
    if (rc == 0) { 
     //printf("%s success\n", msg); 
    } else if (rc == ESRCH) { 
     printf("%s failed with ESRCH\n", msg); 
    } else if (rc == EINVAL) { 
     printf("%s failed with EINVAL\n", msg); 
    } else { 
     printf("%s failed with unknown error: %d\n", msg, rc); 
    } 
} 

static void suspend_handler(int signo) { 
    sem_post(&sem); 
    usleep(SUSPEND_DURATION*1000); 
} 

void installSuspendHandler() { 
    struct sigaction sa; 

    memset(&sa, 0, sizeof(sa)); 

    sigemptyset(&sa.sa_mask); 

    sa.sa_flags = 0; 
    sa.sa_handler = suspend_handler; 

    int rc = sigaction(SUSPEND_SIG, &sa, NULL); 
    checkResults("sigaction SUSPEND", rc); 
} 

void *threadfunc(void *param) { 
    int tid = *((int *) param); 
    free(param); 

    printf("Thread %d entered\n", tid); 

    // this is an example workload, the real app is doing many things 
    while (1) { 
     int rc = sleep(30); 

     if (rc != 0 && errno == EINTR) { 
      //printf("Thread %d got a signal delivered to it\n", tid); 
     } else { 
      //printf("Thread %d did not get expected results! rc=%d, errno=%d\n", tid, rc, errno); 
     } 
    } 

    return NULL; 
} 

int main(int argc, char **argv) { 
    pthread_t threads[NUMTHREADS]; 
    int i; 

    sem_init(&sem, 0, 0); 

    installSuspendHandler(); 

    for(i=0; i<NUMTHREADS; ++i) { 
     int *arg = malloc(sizeof(*arg)); 
     if (arg == NULL) { 
      fprintf(stderr, "Couldn't allocate memory for thread arg.\n"); 
      exit(EXIT_FAILURE); 
     } 

     *arg = i; 
     int rc = pthread_create(&threads[i], NULL, threadfunc, arg); 
     checkResults("pthread_create()", rc); 
    } 

    sleep(3); 

    printf("Will start to send signals...\n"); 

    while (1) { 
     printf("***********************************************\n"); 
     for(i=0; i<NUMTHREADS; ++i) { 
      int rc = pthread_kill(threads[i], SUSPEND_SIG); 
      checkResults("pthread_kill()", rc); 

      printf("Waiting for Semaphore for thread %d ...\n", i); 

      // compute timeout abs timestamp for ack 
      struct timespec ts; 
      clock_gettime(CLOCK_REALTIME, &ts); 
      const int TIMEOUT = SUSPEND_DURATION*1000*1000; // in nano-seconds 

      ts.tv_nsec += TIMEOUT; // timeout to receive ack from signal handler 

      // normalize timespec 
      ts.tv_sec += ts.tv_nsec/1000000000; 
      ts.tv_nsec %= 1000000000; 

      rc = sem_timedwait(&sem, &ts); // try decrement semaphore 

      if (rc == -1 && errno == ETIMEDOUT) { 
       // timeout 
       // semaphore is out of sync 
       printf("Did not received signal handler sem_post before timeout of %d ms for thread %d", TIMEOUT/1000000, i); 
       abort(); 
      } 
      checkResults("sem_timedwait", rc); 
      printf("Received Semaphore for thread %d.\n", i); 
     } 

     sleep(1); 
    } 

    for(i=0; i<NUMTHREADS; ++i) { 
     int rc = pthread_join(threads[i], NULL); 
     checkResults("pthread_join()\n", rc); 
    } 
    printf("Main completed\n"); 
    return 0; 
} 

有问题?

  • 信号是否可能被丢弃并且从未被传送?
  • 系统加载时随机时间导致信号量超时的原因是什么?

usleep()不是异步信号安全功能之中(虽然sleep()是,也有通过它可以产生一个时间延迟等异步信号安全功能)。因此,从信号处理程序调用usleep()的程序不符合要求。这些规范没有描述可能发生的情况 - 既没有这样的调用本身,也没有描述它发生的更大的程序执行。您的问题只能回答符合计划;我在下面做。


  • 是否有可能为一个信号被丢弃,并且永远不会传递?

这取决于你的意思是什么:

  • 如果一个正常的(非实时)的信号传送到一个已经具有信号排队,则没有附加的实例排队线程。

  • 线程可能死亡,信号仍然排队;这些信号将不会被处理。

  • 线程可以改变给定信号的配置(例如,对于SIG_IGN),虽然这是一个per-process属性,而不是每个线程的属性。

  • A thread can block无限期的信号。阻塞的信号不会被丢弃 - 它仍然排队等待线程,并且如果发生这种情况,它最终会在它被解除阻塞之后被接收一段时间。

但是,没有,具有成功地经由kill()raise()功能排队的信号,该信号将不会被随机丢弃。

  • 什么导致信号量在系统加载的随机时间超时?

线程只有在实际运行在内核上时才能接收信号。在具有比核心更多可运行进程的系统上,在任何给定时间,必须暂停某些可运行进程,而不在任何核心上运行时间片。在重负荷的系统,这是常态。信号是异步的,所以你可以发送一个到当前正在等待时间片的线程而不阻塞发送者。那么,完全有可能你的信号线没有被安排在超时到期之前运行。如果它确实运行,它可能会因为某种原因而阻塞信号,并且在占用其时间片之前不会解除阻塞它。


最终,你可以用你的信号量的方法来检查目标线程是否办理您所选择的任何超时内的信号,但不能提前预知它需要多长时间的线程来处理信号,甚至不管它是否会在有限的时间内这样做(例如,在这样做之前它可能因为某种原因死亡)。

+0

我知道usleep不是异步信号安全的,但是睡眠和睡眠方面我都有同样的问题。我运行了其他测试,似乎信号处理程序可能需要几秒钟才能执行,并且它只发生在执行SD卡上的io的线程上。难道SD卡上的IO延迟了信号处理吗? –

+0

@GuillaumeMICHEL在SD卡上执行I/O操作期间,线程完全有可能在整个持续时间内阻塞信号。然而,我会惊讶地发现发送到这样的线程的信号被丢失了 - 这将是不合格的。 –