简体   繁体   English

如何在C中干净地终止线程?

[英]How to terminate threads cleanly in C?

I am trying to write a multithreaded application in C for the Raspberry Pi in raspbian environment (UNIX system). 我正在尝试用C语言为Raspberry Pi在raspbian环境(UNIX系统)中编写一个多线程应用程序。

Apart from the main thread three other threads are created and do the following: 除主线程外,还创建了三个其他线程并执行以下操作:

  1. the first looks at the output of a PIR sensor and if movement is detected it takes a picture. 第一个看PIR传感器的输出,如果检测到移动则需要拍照。 The thread function is task1() ; 线程函数是task1() ;
  2. the second uses sigwait and alarm() to measure the temperature every given seconds. 第二个使用sigwaitalarm()来测量每一秒的温度。 The thread function is task2() 线程函数是task2()
  3. The third thread checks if a new picture is taken and if so it does some other stuff. 第三个线程检查是否拍摄了新照片,如果是,则执行其他一些操作。 The synchronization with the first thread is done with a global flag, a mutex and with pthread_cond_wait . 与第一个线程的同步是通过全局标志,互斥锁和pthread_cond_wait The thread function is task3() . 线程函数是task3()

All the thread functions have an infinite loop. 所有线程函数都有一个无限循环。 The execution of the program seems good. 程序的执行似乎很好。

The main thread call the function pause() and then pthread_cancel() to exit cleanly from each thread (lowering the pins). 主线程调用函数pause()然后pthread_cancel()从每个线程中干净地退出(降低引脚)。 At first I did not use the signal handler and the process quit without calling the exiting thread functions registered with the function pthread_cleanup_push . 起初我没有使用信号处理程序和进程退出而没有调用使用函数pthread_cleanup_push注册的退出线程函数。 This because pause() returns only if the handler returns. 这是因为pause()仅在处理程序返回时返回。 That is why I added my signal handler which returns. 这就是为什么我添加了返回的信号处理程序。

In this way the pthread_cancel are called correctly and also the exiting thread functions are called correctly (the output is printed) but the process keeps running even with pressing CTRL-C or calling kill from another terminal window. 通过这种方式正确调用pthread_cancel并且还正确调用了现有的线程函数(输出被打印),但即使按下CTRL-C或从另一个终端窗口调用kill,进程也会继续运行。

I think I messed up with the masks so that the signal generated by pthread_cancel (if any) has no effect. 我想我搞砸了面具,这样pthread_cancel (如果有的话)产生的信号没有效果。

Apart from this I have read that in general it is bad practice using pthread_cancel so my question is: 除此之外,我已经读过,一般来说使用pthread_cancel是不好的做法所以我的问题是:

what is the best way to exit cleanly from each thread (especially in my case)? 从每个线程中干净地退出的最佳方法是什么(特别是在我的情况下)? Shall I use another global flag? 我要用另一个全球旗帜吗? With mutex or read-write lock? 使用互斥锁或读写锁? Should I set it from the main thread or handler? 我应该从主线程还是处理程序设置它?

Any suggestion will be appreciated. 任何建议将不胜感激。

EDIT: If instead of calling pthread_cancel I use a global flag for the infinite loops, how would you set the condition in task3() ? 编辑:如果不是调用pthread_cancel而是使用全局标志来表示无限循环,那么如何在task3()设置条件?

NOTE: the code is incomplete for the sake of brevity. 注意:为简洁起见,代码不完整。 I tried to emphasize the logic. 我试图强调逻辑。 If needed I will add all the code. 如果需要,我将添加所有代码。

#include<wiringPi.h>  
#include<stdlib.h>
#include<stdio.h>
#include<signal.h>
#include<stdint.h>
#include<pthread.h>

g_new_pic_flag=FALSE;
pthread_cond_t g_new_pic_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t g_new_pic_m = PTHREAD_MUTEX_INITIALIZER;

/* FUNCTION DECLARATION */

/*We define thread exit functions so that each pin 
is lowered by the thread in which it is used avoiding
race condition between the signal handler of the main thread
and the other threads*/
void exitingThreadTask1(void* arg);
void exitingThreadTask2(void* arg);
void exitingThreadTask3(void* arg);

void* task1(void *arg); //thread function for the motion sensor
void* task2(void *arg); //thread function for the temperature reading
void* task3(void *arg); //thread function to post data on IOT platforms

/*Signal handler to return from pause*/
void sig_handler(int signo);

int main()
{
    int err;
    sigset_t omask, mask;
    pthread_t thread_motionSensor;
    pthread_t thread_tempReading;
    pthread_t thread_platformPost;

    printf("Created threads IDs\n");

    if (wiringPiSetup()<0)
    {
        printf("WiringPi error\n");
        return -1;
    }
    printf("WiringPi is ok\n");

    if (signal(SIGQUIT, sig_handler)==SIG_ERR)
        printf("Error on recording SIGQUITHANDLER\n");
    if (signal(SIGINT, sig_handler)==SIG_ERR)
        printf("Error on recording SIGINTHANDLER\n");
    if (signal(SIGTERM, sig_handler)==SIG_ERR)
        printf("Error on recording SIGTERMHANDLER\n");

    /*Create a new mask to block all signals for the following thread*/
    sigfillset(&mask);
    pthread_sigmask(SIG_SETMASK, &mask, &omask);
    printf("Trying to create threads\n");
    if ((err = pthread_create (&thread_motionSensor, NULL, task1, NULL))!=0)
    {
    printf("Thread 1 not created: error %d\n", err);
        err_exit((const char)err, "pthread_create error");
    }
    printf("Thread 1 created. Trying to create Thread 2\n");
    if((err = pthread_create (&thread_tempReading,   NULL, task2, NULL))!=0)
    {
    printf("Thread 2 not created: error %d\n", err);
        err_exit((const char)err, "pthread_create error");
    }
    printf("Thread 2 created. Trying to create Thread 3\n");
    if ((err = pthread_create (&thread_platformPost, NULL, task3, NULL))!=0)
    {
     printf("Thread 3 not created: error %d %d\n", err);
         err_exit((const char)err, "pthread_create error");
    }
    printf("Thread 3 created\n");
    /*The main thread must block the SIGALRM but catch SIGINT
    SIGQUIT, SIGTERM, SIgkILL*/
    sigemptyset(&omask);
    sigaddset(&omask, SIGINT);
    sigaddset(&omask, SIGQUIT);
    sigaddset(&omask, SIGKILL);
    sigaddset(&omask, SIGTERM);

    pthread_sigmask(SIG_UNBLOCK, &omask, NULL);
    printf("Main thread waiting for signal\n");
    pause();
    printf("Exit signal received: cancelling threads\n");

    pthread_cancel(thread_motionSensor);
    pthread_cancel(thread_tempReading);
    pthread_cancel(thread_platformPost);
    pthread_join(thread_motionSensor, NULL);
    pthread_join(thread_tempReading,  NULL);
    pthread_join(thread_platformPost, NULL);
    printf("Exiting from main thread and process\n");
    exit(0);
}

void* task1(void *arg)
{
    //INITIALIZING
    pthread_cleanup_push(exitingThreadTask1, NULL);
    while(1)
    {
        //do stuff1
    }
    pthread_cleanup_pop(0);
    pthread_exit(0);

}

void* task2(void *arg)
{
    static const unsigned char schedule_time = 5;
    int signo, err;
    /*
    We set a local mask with SIGALARM for the function sigwait
    All signals have already been blocked
    */
    sigset_t alarm_mask;
    sigemptyset(&alarm_mask);
    sigaddset(&alarm_mask, SIGALRM);
    alarm(schedule_time);
    pthread_cleanup_push(exitingThreadTask2, NULL);
    while (1)
    {
        err = sigwait(&alarm_mask, &signo); //signo == SIGALRM check
        if (err!=0)
            err_exit(err, "sigwait failed\n");
        //do stuff
        alarm(schedule_time);
    }
    pthread_cleanup_pop(0);
    pthread_exit(0);
}

void* task3(void *arg)
{
    pthread_cleanup_push(exitingThreadTask3, NULL);
    while(1)
    {
        pthread_mutex_lock(&g_new_pic_m);
        while(g_new_pic_flag==FALSE)
        {
            pthread_cond_wait(&g_new_pic_cond, &g_new_pic_m);
        }
        pthread_mutex_unlock(&g_new_pic_m);
        //do stuff
    }
    pthread_cleanup_pop(0);
    pthread_exit(0);

}

void exitingThreadTask1(void* arg)
{
    printf("Thread of task 1 exiting\n");
    digitalWrite(OUTPIN, LOW);
    digitalWrite(INPIN, LOW);
    printf("Pins lowered\n");
    pthread_exit((void*)0);
}

void exitingThreadTask2(void* arg)
{
    printf("Thread of task 2 exiting\n");
    digitalWrite(DHTPIN, LOW);
    printf("Pin lowered\n");
    pthread_exit((void*)0);
}

void exitingThreadTask3(void* arg)
{
    printf("Thread of task 3 exiting\n");
    pthread_exit((void*)0);
}

void sig_handler(int signo)
{
    printf("Running handler to return from pause\n");
    return;
}

In general, I recommend not cancelling or killing threads. 一般来说,我建议不要取消或杀死线程。 I also try to minimize signal handling in threaded applications, or at least make the signal handlers very short, nonblocking and simple. 我还尝试最小化线程应用程序中的信号处理,或者至少使信号处理程序非常短,无阻塞且简单。 It is better to have the threads run a loop, where they eg check a cancel flag, or if your thread does I/O with select or epoll, have the master thread write to a pipe to signal the other end to die. 最好让线程运行一个循环,例如检查取消标志,或者如果你的线程使用select或epoll进行I / O操作,让主线程写入管道以指示另一端死亡。 With C++ and pthreads, cancelling or killing can be even more disastrous, so for C++, doing a clean shutdown with custom code is even more important. 使用C ++和pthreads,取消或终止可能更加灾难性,因此对于C ++来说,使用自定义代码进行干净关闭更为重要。

See eg pthread cancel and C++ 请参阅例如pthread cancel和C ++

You must not call pthread_exit() in the cleanup functions, because pthread_exit() will also call the cleanup function registered for the thread. 您不能在清理函数中调用pthread_exit() ,因为pthread_exit()也将调用为该线程注册的清理函数。

So, in your program, the cleanup function is called recursively and the threads never exit. 因此,在您的程序中,清理函数是递归调用的,并且线程永远不会退出。

About the kill from another terminal, the command kill -9 and the pid of the process should always work because SIGKILL can't be ignored nor caught. 关于来自另一个终端的kill,命令kill -9和进程的pid应该始终有效,因为SIGKILL不能被忽略也不能被捕获。

And in the signal handler function, you have to use async-signal-safe functions, printf() isn't async-signal-safe. 在信号处理函数中,你必须使用异步信号安全函数, printf()不是异步信号安全的。

Another way to wait for a signal in the main thread is to use sigwait() or sigwaitinfo() instead of pause() , like you did for SIGALARM in a thread. 在主线程中等待信号的另一种方法是使用sigwait()sigwaitinfo()而不是pause() ,就像在线程中对SIGALARM所做的那样。 So it won't need to register a handler function, but it will need to block the signals to be caught in all threads. 因此它不需要注册处理函数,但它需要阻止所有线程中捕获的信号。

EDIT: To answer your last comment. 编辑:回答你的上一个评论。

Exiting the threads task2() and task3() with a flag seems to be complex, because the main thread have to send SIGALRM to task2 in order to wake it up, and also signal the condition in order to wake up task3 . 使用标志退出线程task2()task3()似乎很复杂,因为主线程必须将SIGALRM发送到task2才能将其唤醒,并且还要发出条件信号以唤醒task3

I modified your code to try to use a flag, but i may have missed an eventual problem because synchronizing threads may be complex. 我修改了你的代码以尝试使用标志,但我可能错过了最终的问题,因为同步线程可能很复杂。

In the case of your program, I haven't enough knwoledge to say if it is better to use pthread_cancel() and pthread_testcancel() , or to use flags. 在程序的情况下,我没有足够的知识来说明使用pthread_cancel()pthread_testcancel()是否更好,或者使用标志。 However, pthread_cancel() seems to be able to cancel without synchronization problems, threads that are waiting for signals or for a condition. 但是, pthread_cancel()似乎能够在没有同步问题,等待信号的线程或条件的情况下取消。

Using a flag, for task3 , there could be the following problem: 使用标志,对于task3 ,可能存在以下问题:

  1. task3 check the flag that is 0 task3检查0的标志
  2. main thread set the flag to 1 主线程将标志设置为1
  3. main thread signal the condition 主线程发出信号
  4. task3 begin to wait for the condition task3开始等待这个条件

In this case, thread task3 won't exit, because it wasn't waiting when the condition was signaled. 在这种情况下,线程task3将不会退出,因为它在条件发出信号时没有等待。 I'am not sure, but this problem is maybe avoided by protecting the flag with the same mutex we use for the condition. 我不确定,但是可以通过使用我们用于条件的相同互斥锁来保护标志来避免这个问题。 Because when the flag will be set and the condition signaled, task3 will be waiting for the condition or doing work out of the critical section. 因为当标志被设置并且条件发出信号时, task3将等待条件或从临界区开始工作。

I don't know if there may be a problem for task2 , for example if the signal is lost due to an internal problem, but normally, the signal will be pending. 我不知道task2是否存在问题,例如,如果信号由于内部问题而丢失,但通常情况下,信号将处于待处理状态。

Here is the code of my test. 这是我测试的代码。 I placed 1 as argument for the function pthread_cleanup_pop() , to make the threads execute the cleanup functions. 我将1作为函数pthread_cleanup_pop() ,以使线程执行清理函数。

#include<stdlib.h>
#include<stdio.h>
#include<signal.h>
#include<stdint.h>
#include<pthread.h>

#define FALSE 0
volatile sig_atomic_t g_new_pic_flag=FALSE;
pthread_cond_t g_new_pic_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t g_new_pic_m = PTHREAD_MUTEX_INITIALIZER;
volatile int g_shutdown_task_3 = 0;

volatile int g_shutdown_task_1_2 = 0;
pthread_mutex_t g_shutdown_mutex = PTHREAD_MUTEX_INITIALIZER;
/* FUNCTION DECLARATION */

/*We define thread exit functions so that each pin 
is lowered by the thread in which it is used avoiding
race condition between the signal handler of the main thread
and the other threads*/
void exitingThreadTask1(void* arg);
void exitingThreadTask2(void* arg);
void exitingThreadTask3(void* arg);

void* task1(void *arg); //thread function for the motion sensor
void* task2(void *arg); //thread function for the temperature reading
void* task3(void *arg); //thread function to post data on IOT platforms

/*Signal handler to return from pause*/
void sig_handler(int signo);

void err_exit(char err, char *msg) {
  printf("\nError: %s\n",msg);
  exit(1);
}

int main()
{
    int err;
    sigset_t omask, mask;
    pthread_t thread_motionSensor;
    pthread_t thread_tempReading;
    pthread_t thread_platformPost;

    printf("Created threads IDs\n");
    /*
    if (wiringPiSetup()<0)
    {
        printf("WiringPi error\n");
        return -1;
    }
    */
    printf("WiringPi is ok\n");

    if (signal(SIGQUIT, sig_handler)==SIG_ERR)
        printf("Error on recording SIGQUITHANDLER\n");
    if (signal(SIGINT, sig_handler)==SIG_ERR)
        printf("Error on recording SIGQUITHANDLER\n");
    if (signal(SIGTERM, sig_handler)==SIG_ERR)
        printf("Error on recording SIGQUITHANDLER\n");

    /*Create a new mask to block all signals for the following thread*/
    sigfillset(&mask);
    pthread_sigmask(SIG_SETMASK, &mask, &omask);
    printf("Trying to create threads\n");
    if ((err = pthread_create (&thread_motionSensor, NULL, task1, NULL))!=0)
    {
    printf("Thread 1 not created: error %d\n", err);
        err_exit((const char)err, "pthread_create error");
    }
    printf("Thread 1 created. Trying to create Thread 2\n");
    if((err = pthread_create (&thread_tempReading,   NULL, task2, NULL))!=0)
    {
    printf("Thread 2 not created: error %d\n", err);
        err_exit((const char)err, "pthread_create error");
    }
    printf("Thread 2 created. Trying to create Thread 3\n");
    if ((err = pthread_create (&thread_platformPost, NULL, task3, NULL))!=0)
    {
     printf("Thread 3 not created: error %d %d\n", err);
         err_exit((const char)err, "pthread_create error");
    }
    printf("Thread 3 created\n");
    /*The main thread must block the SIGALRM but catch SIGINT
    SIGQUIT, SIGTERM, SIgkILL*/
    sigemptyset(&omask);
    sigaddset(&omask, SIGINT);
    sigaddset(&omask, SIGQUIT);
    sigaddset(&omask, SIGKILL);
    sigaddset(&omask, SIGTERM);

    pthread_sigmask(SIG_UNBLOCK, &omask, NULL);
    printf("Main thread waiting for signal\n");
    pause();
    printf("Exit signal received: cancelling threads\n");


    pthread_mutex_lock(&g_shutdown_mutex);
    g_shutdown_task_1_2 = 1;
    pthread_mutex_unlock(&g_shutdown_mutex);
    pthread_mutex_lock(&g_new_pic_m);
    g_shutdown_task_3 = 1;
    pthread_cond_signal(&g_new_pic_cond);
    pthread_mutex_unlock(&g_new_pic_m);

    pthread_kill(thread_tempReading,SIGALRM);


    pthread_join(thread_motionSensor, NULL);
    pthread_join(thread_tempReading,  NULL);
    pthread_join(thread_platformPost, NULL);
    printf("Exiting from main thread and process\n");
    exit(0);
}

void* task1(void *arg)
{
    //INITIALIZING
    pthread_cleanup_push(exitingThreadTask1, NULL);
    while(1)
    {
        pthread_mutex_lock(&g_shutdown_mutex);
        if(g_shutdown_task_1_2) {
          pthread_mutex_unlock(&g_shutdown_mutex);
          break;
        }
        pthread_mutex_unlock(&g_shutdown_mutex);
        //do stuff1
        sleep(1);
    }
    pthread_cleanup_pop(1);
    pthread_exit(0);

}

void* task2(void *arg)
{
    static const unsigned char schedule_time = 5;
    int signo, err;
    /*
    We set a local mask with SIGALARM for the function sigwait
    All signals have already been blocked
    */
    sigset_t alarm_mask;
    sigemptyset(&alarm_mask);
    sigaddset(&alarm_mask, SIGALRM);
    alarm(schedule_time);
    pthread_cleanup_push(exitingThreadTask2, NULL);
    while (1)
    {
        pthread_mutex_lock(&g_shutdown_mutex);
        if(g_shutdown_task_1_2) {
          pthread_mutex_unlock(&g_shutdown_mutex);
          break;
        }
        pthread_mutex_unlock(&g_shutdown_mutex);

        err = sigwait(&alarm_mask, &signo); //signo == SIGALRM check
        if (err!=0)
            err_exit(err, "sigwait failed\n");

        pthread_mutex_lock(&g_shutdown_mutex);
        if(g_shutdown_task_1_2) {
          pthread_mutex_unlock(&g_shutdown_mutex);
          break;
        }
        pthread_mutex_unlock(&g_shutdown_mutex);

        //do stuff
        alarm(schedule_time);
    }
    pthread_cleanup_pop(1);
    pthread_exit(0);
}

void* task3(void *arg)
{
    pthread_cleanup_push(exitingThreadTask3, NULL);
    while(1)
    {
        pthread_mutex_lock(&g_new_pic_m);
        if(g_shutdown_task_3) {
          pthread_mutex_unlock(&g_new_pic_m);
          break;
        }
        while(g_new_pic_flag==FALSE)
        {
            if(g_shutdown_task_3) break;

            pthread_cond_wait(&g_new_pic_cond, &g_new_pic_m);

            if(g_shutdown_task_3) break;
        }
        if(g_shutdown_task_3) {
          pthread_mutex_unlock(&g_new_pic_m);
          break;
        }
        pthread_mutex_unlock(&g_new_pic_m);
        //do stuff
    }
    pthread_cleanup_pop(1);
    pthread_exit(0);

}

void exitingThreadTask1(void* arg)
{
    printf("Thread of task 1 exiting\n");
    //digitalWrite(OUTPIN, LOW);
    //digitalWrite(INPIN, LOW);
    printf("Pins lowered\n");
}

void exitingThreadTask2(void* arg)
{
    printf("Thread of task 2 exiting\n");
    //digitalWrite(DHTPIN, LOW);
    printf("Pin lowered\n");
}

void exitingThreadTask3(void* arg)
{
    printf("Thread of task 3 exiting\n");
}

void sig_handler(int signo)
{
    return;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM