来自并行线程的中断阻塞调用

3
以下简单的代码展示了我遇到的问题:如何唤醒一个正在阻塞在驱动程序调用上的线程。我没有驱动程序源代码,但是需要唤醒线程以关闭进程,如果驱动程序所阻塞的物理设备发生了电力中断。
示例(见下面的代码): main()SIGINT设置信号处理程序,启动子线程,然后进入无限循环,在该循环中,它反复休眠、唤醒并打印它何时唤醒。子线程也做同样的事情,但每次唤醒时,它还向进程发送一个SIGINT。使用sleep()作为简单的阻塞调用。
运行代码会显示以下行为:
- 每次子线程唤醒时,它都会打印出它的“When”消息(何时唤醒)。然后信号处理程序报告接收到了SIGINT。 - 每当主线程唤醒时,它都会打印出它的“When”消息。
main: 0.127

Child: 0.127

SignalHandler: signum = 2

Child: 5.127

SignalHandler: signum = 2

main: 7.127

Child: 10.127

SignalHandler: signum = 2

main: 14.127

Child: 15.128

SignalHandler: signum = 2

Child: 20.128

SignalHandler: signum = 2

main: 21.127

.
.
.

我原本期望当进程接收到 SIGINT 信号时,main() 函数会跳出其对 sleep() 函数的阻塞调用。这将被视为 main() 打印其 'When' 消息。实际上,如果我从 shell 中键入 Ctrl-C (SIGINT),我确实看到了这种行为。下面可以看到两个 Ctrl-C 中断。每次,main() 确实跳出了其对 sleep() 的阻塞调用:

main: 0.417

child: 0.417

SignalHandler: signum = 2

child: 5.417

SignalHandler: signum = 2

^C

SignalHandler: signum = 2

main: 6.664

child: 10.417

SignalHandler: signum = 2

main: 13.664

child: 15.418

SignalHandler: signum = 2

^C

SignalHandler: signum = 2

main: 16.680

child: 20.418

SignalHandler: signum = 2

main: 23.680

child: 25.418

SignalHandler: signum = 2

----------- 代码开始 ---------------

// Compile using:
//   g++ Test.cpp -o Test -g -lpthread -lrt

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include  <time.h>

void When(const char *who) {
  timespec sTimespec;
  static long startSecond = -1;

  clock_gettime(CLOCK_MONOTONIC, &sTimespec);

  if (startSecond == -1)
    startSecond = sTimespec.tv_sec;

  sTimespec.tv_sec -= startSecond;
  sTimespec.tv_nsec /= 1e+6; // only show milliseconds
  printf("%s: %ld.%03ld\n", who, sTimespec.tv_sec, sTimespec.tv_nsec);
}

void SignalHandler(int signum) {
  printf("SignalHandler: signum = %d\n", signum);
}

void *Child(void *pContext) {
  char temp[256];

  for (;;) {
    When("Child");
    kill(getpid(), SIGINT);
    sleep(5);
  } 
}

int main(int argc, char **argv) {
  pthread_t childThread; 

  signal(SIGINT, SignalHandler);
  pthread_create(&childThread, NULL, Child, NULL); 

  for (;;) {
    When("main");
    sleep(7);
  }  

  return 0;
}

看起来当一个进程向自己发送SIGINT信号时,该信号会被传递(参见第一条结果),但是阻塞的调用不会被中断。从外部进程发送SIGINT会传递信号并中断阻塞调用。

我尝试过其他事情:

raise(SIGINT);

pthread_kill(mainThreadId, SIGINT); // where mainThreadId is resolved to pthread_self() from within 'main'

sprintf(temp, “kill -2 %d”, getpid()); system(temp);

我写了一个独立的程序(xkill),它接受一个PID作为参数,并向该PID发送SIGINT信号。然后:
sprintf(temp, “xkill %d”, getpid()); system(temp);  

注意:我可以从shell中运行此程序并获得良好/预期的行为。
据我看来,在父进程的阻塞调用中,没有任何子线程或进程可以中断它,即使它可以传递信号。
您们中有人能为这种情况提供一些帮助吗?
在我的真实代码中,我需要编程地打破阻塞驱动程序调用,以便我可以干净地关闭。在我的真实代码中,子线程是设备健康监视器。

只需安排线程在退出函数时仅执行无害操作,例如检查函数返回时的取消标志并设置该取消标志。这样,您就不必关心线程是否会从函数中退出。 - David Schwartz
1
进程中的任何线程都可以用来传递信号;仅当信号传递到相同线程时,阻塞调用才会被中断。(即使信号处理程序为空。)因此,您需要在除执行阻塞调用的线程之外的所有线程中阻止该信号,或者您可以使用将信号转发到所需线程的信号处理程序(除非已由所需线程捕获)。保罗·格里菲斯已经提供了一个相当不错的后者示例,尽管我想可能还有更简单/更直接的示例。 - Nominal Animal
1个回答

0

经过一些小的修改,在我的系统上(Debian Linux - 如果我去掉 clock_gettime() 部分,它在 Mac OS X 上也能以同样的方式工作)使用 pthread_kill(),似乎可以按照您的要求工作:

#define _POSIX_C_SOURCE 200809L

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <stdbool.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>

void When(const char *who) {
    struct timespec sTimespec;
    static long startSecond = -1;

    clock_gettime(CLOCK_MONOTONIC, &sTimespec);

    if (startSecond == -1) {
        startSecond = sTimespec.tv_sec;
    }

    sTimespec.tv_sec -= startSecond;
    sTimespec.tv_nsec /= 1e+6;  // only show milliseconds
    printf("%s: %ld.%03ld\n", who, sTimespec.tv_sec, sTimespec.tv_nsec);
}

void SignalHandler(int signum) {
    printf("SignalHandler: signum = %d\n", signum);
}

void *Child(void *pContext) {
    pthread_t * main_tid = pContext;

    while ( true ) {
        When("Child");
        pthread_kill(*main_tid, SIGINT);
        sleep(5);
    }
}

int main(void) {
    pthread_t childThread;

    struct sigaction sa;
    sa.sa_handler = SignalHandler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("thread: couldn't set SIGINT handler");
        exit(EXIT_FAILURE);
    };

    pthread_t own_tid = pthread_self();
    pthread_create(&childThread, NULL, Child, &own_tid);

    while ( true ) {
        When("main");
        sleep(7);
    }

    return EXIT_SUCCESS;
}

并输出:

paul@local:~/src/c/scratch$ gcc -o thread thread.c -pthread -std=c99 -Wall -Wextra -pedantic -lpthread -lrt
paul@local:~/src/c/scratch$ ./thread
main: 0.464
Child: 0.464
SignalHandler: signum = 2
main: 0.464
Child: 5.464
SignalHandler: signum = 2
main: 5.464
Child: 10.464
SignalHandler: signum = 2
main: 10.464
Child: 15.464
SignalHandler: signum = 2
main: 15.465
^Z
[1]+  Stopped                 ./thread
paul@local:~/src/c/scratch$ kill %1

[1]+  Stopped                 ./thread
paul@local:~/src/c/scratch$ 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接