2017-07-06 54 views
1

在現有多線程應用程序的上下文中,我想暫停特定持續時間的線程列表,然後恢復其正常執行。我知道你們中有些人會說我不應該這樣做,但我知道這一點,我沒有選擇。如何通過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; 
} 

有問題?

  • 信號是否可能被丟棄並且從未被傳送?
  • 系統加載時隨機時間導致信號量超時的原因是什麼?

回答

1

usleep()不是異步信號安全功能之中(雖然sleep()是,也有通過它可以產生一個時間延遲等異步信號安全功能)。因此,從信號處理程序調用usleep()的程序不符合要求。這些規範沒有描述可能發生的情況 - 既沒有這樣的調用本身,也沒有描述它發生的更大的程序執行。您的問題只能回答符合計劃;我在下面做。


  • 是否有可能爲一個信號被丟棄,並且永遠不會傳遞?

這取決於你的意思是什麼:

  • 如果一個正常的(非實時)的信號傳送到一個已經具有信號排隊,則沒有附加的實例排隊線程。

  • 線程可能死亡,信號仍然排隊;這些信號將不會被處理。

  • 線程可以改變給定信號的配置(例如,對於SIG_IGN),雖然這是一個per-process屬性,而不是每個線程的屬性。

  • A thread can block無限期的信號。阻塞的信號不會被丟棄 - 它仍然排隊等待線程,並且如果發生這種情況,它最終會在它被解除阻塞之後被接收一段時間。

但是,沒有,具有成功地經由kill()raise()功能排隊的信號,該信號將不會被隨機丟棄。

  • 什麼導致信號量在系統加載的隨機時間超時?

線程只有在實際運行在內核上時才能接收信號。在具有比核心更多可運行進程的系統上,在任何給定時間,必須暫停某些可運行進程,而不在任何核心上運行時間片。在重負荷的系統,這是常態。信號是異步的,所以你可以發送一個到當前正在等待時間片的線程而不阻塞發送者。那麼,完全有可能你的信號線沒有被安排在超時到期之前運行。如果它確實運行,它可能會因爲某種原因而阻塞信號,並且在佔用其時間片之前不會解除阻塞它。


最終,你可以用你的信號量的方法來檢查目標線程是否辦理您所選擇的任何超時內的信號,但不能提前預知它需要多長時間的線程來處理信號,甚至不管它是否會在有限的時間內這樣做(例如,在這樣做之前它可能因爲某種原因死亡)。

+0

我知道usleep不是異步信號安全的,但是睡眠和睡眠方面我都有同樣的問題。我運行了其他測試,似乎信號處理程序可能需要幾秒鐘才能執行,並且它只發生在執行SD卡上的io的線程上。難道SD卡上的IO延遲了信號處理嗎? –

+0

@GuillaumeMICHEL在SD卡上執行I/O操作期間,線程完全有可能在整個持續時間內阻塞信號。然而,我會驚訝地發現發送到這樣的線程的信號被丟失了 - 這將是不合格的。 –