如何在 C 中干净地终止线程?
How to terminate threads cleanly in C?
我正在尝试在 raspbian 环境(UNIX 系统)中用 C 为 Raspberry Pi 编写一个多线程应用程序。
除了主线程之外,还创建了另外三个线程并执行以下操作:
- 首先查看 PIR 传感器的输出,如果检测到运动,则会拍照。线程函数为
task1()
;
- 第二个使用
sigwait
和 alarm()
每隔给定秒测量一次温度。线程函数为task2()
- 第三个线程检查是否拍摄了新照片,如果是,它会做一些其他事情。与第一个线程的同步是通过全局标志、互斥锁和
pthread_cond_wait
完成的。线程函数为task3()
.
所有的线程函数都有一个无限循环。程序的执行看起来不错。
主线程调用函数pause()
然后pthread_cancel()
从每个线程干净地退出(降低引脚)。
起初我没有使用信号处理程序,进程退出时没有调用注册到函数 pthread_cleanup_push
的退出线程函数。这是因为 pause()
returns 仅当处理程序 returns 时。这就是为什么我添加了我的信号处理程序 returns.
通过这种方式,pthread_cancel
被正确调用,退出的线程函数也被正确调用(输出被打印),但进程保持 运行 即使按下 CTRL-C 或调用 kill来自另一个终端 window。
我想我弄乱了掩码,所以 pthread_cancel
(如果有的话)产生的信号没有效果。
除此之外,我读到一般来说使用 pthread_cancel
是不好的做法,所以我的问题是:
从每个线程中干净退出的最佳方法是什么(尤其是在我的情况下)?我应该使用另一个全球标志吗?使用互斥锁或读写锁?我应该从主线程还是处理程序设置它?
如有任何建议,我们将不胜感激。
编辑:如果我不调用 pthread_cancel
而是为无限循环使用全局标志,您将如何设置 task3()
中的条件?
注意:为简洁起见,代码不完整。我试图强调逻辑。如果需要,我会添加所有代码。
#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;
}
不能在清理函数中调用pthread_exit()
,因为pthread_exit()
也会调用为线程注册的清理函数。
因此,在您的程序中,清理函数被递归调用并且线程永远不会退出。
关于从另一个终端终止,命令 kill -9
和进程的 pid 应该始终有效,因为 SIGKILL 不能被忽略或捕获。
并且在信号处理函数中,您必须使用异步信号安全函数,printf()
不是异步信号安全的。
在主线程中等待信号的另一种方法是使用 sigwait()
或 sigwaitinfo()
而不是 pause()
,就像您在线程中对 SIGALARM 所做的那样。所以它不需要注册一个处理函数,但是它需要阻塞所有线程捕获的信号。
编辑:回答您最后的评论。
用标志退出线程 task2()
和 task3()
似乎很复杂,因为主线程必须发送 SIGALRM 到 task2
才能唤醒它,而且发出条件信号以唤醒 task3
.
我修改了您的代码以尝试使用标志,但我可能错过了最终的问题,因为同步线程可能很复杂。
就您的程序而言,我没有足够的知识来判断是使用 pthread_cancel()
和 pthread_testcancel()
还是使用标志更好。但是,pthread_cancel()
似乎能够在没有同步问题、正在等待信号或条件的线程的情况下取消。
使用flag,对于task3
,可能会出现以下问题:
- task3 检查
0
的标志
- 主线程设置标志为
1
- 主线程发出条件信号
- task3开始等待条件
在这种情况下,线程 task3
不会退出,因为它在发出条件信号时没有等待。我不确定,但可以通过使用我们用于条件的相同互斥锁保护标志来避免这个问题。因为当设置标志并发出条件信号时,task3
将等待条件或在临界区外工作。
我不知道 task2
是否有问题,例如由于内部问题导致信号丢失,但通常情况下,信号会挂起。
这是我的测试代码。我将 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;
}
一般来说,我建议不要取消或杀死线程。我还尝试尽量减少线程应用程序中的信号处理,或者至少使信号处理程序非常短、非阻塞和简单。最好让线程 运行 有一个循环,例如检查取消标志,或者如果您的线程使用 select 或 epoll 执行 I/O,则让主线程写入管道以指示另一端死亡。对于 C++ 和 pthreads,取消或终止可能更具有灾难性,因此对于 C++,使用自定义代码进行干净关闭更为重要。
参见例如pthread cancel and C++
我正在尝试在 raspbian 环境(UNIX 系统)中用 C 为 Raspberry Pi 编写一个多线程应用程序。
除了主线程之外,还创建了另外三个线程并执行以下操作:
- 首先查看 PIR 传感器的输出,如果检测到运动,则会拍照。线程函数为
task1()
; - 第二个使用
sigwait
和alarm()
每隔给定秒测量一次温度。线程函数为task2()
- 第三个线程检查是否拍摄了新照片,如果是,它会做一些其他事情。与第一个线程的同步是通过全局标志、互斥锁和
pthread_cond_wait
完成的。线程函数为task3()
.
所有的线程函数都有一个无限循环。程序的执行看起来不错。
主线程调用函数pause()
然后pthread_cancel()
从每个线程干净地退出(降低引脚)。
起初我没有使用信号处理程序,进程退出时没有调用注册到函数 pthread_cleanup_push
的退出线程函数。这是因为 pause()
returns 仅当处理程序 returns 时。这就是为什么我添加了我的信号处理程序 returns.
通过这种方式,pthread_cancel
被正确调用,退出的线程函数也被正确调用(输出被打印),但进程保持 运行 即使按下 CTRL-C 或调用 kill来自另一个终端 window。
我想我弄乱了掩码,所以 pthread_cancel
(如果有的话)产生的信号没有效果。
除此之外,我读到一般来说使用 pthread_cancel
是不好的做法,所以我的问题是:
从每个线程中干净退出的最佳方法是什么(尤其是在我的情况下)?我应该使用另一个全球标志吗?使用互斥锁或读写锁?我应该从主线程还是处理程序设置它?
如有任何建议,我们将不胜感激。
编辑:如果我不调用 pthread_cancel
而是为无限循环使用全局标志,您将如何设置 task3()
中的条件?
注意:为简洁起见,代码不完整。我试图强调逻辑。如果需要,我会添加所有代码。
#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;
}
不能在清理函数中调用pthread_exit()
,因为pthread_exit()
也会调用为线程注册的清理函数。
因此,在您的程序中,清理函数被递归调用并且线程永远不会退出。
关于从另一个终端终止,命令 kill -9
和进程的 pid 应该始终有效,因为 SIGKILL 不能被忽略或捕获。
并且在信号处理函数中,您必须使用异步信号安全函数,printf()
不是异步信号安全的。
在主线程中等待信号的另一种方法是使用 sigwait()
或 sigwaitinfo()
而不是 pause()
,就像您在线程中对 SIGALARM 所做的那样。所以它不需要注册一个处理函数,但是它需要阻塞所有线程捕获的信号。
编辑:回答您最后的评论。
用标志退出线程 task2()
和 task3()
似乎很复杂,因为主线程必须发送 SIGALRM 到 task2
才能唤醒它,而且发出条件信号以唤醒 task3
.
我修改了您的代码以尝试使用标志,但我可能错过了最终的问题,因为同步线程可能很复杂。
就您的程序而言,我没有足够的知识来判断是使用 pthread_cancel()
和 pthread_testcancel()
还是使用标志更好。但是,pthread_cancel()
似乎能够在没有同步问题、正在等待信号或条件的线程的情况下取消。
使用flag,对于task3
,可能会出现以下问题:
- task3 检查
0
的标志
- 主线程设置标志为
1
- 主线程发出条件信号
- task3开始等待条件
在这种情况下,线程 task3
不会退出,因为它在发出条件信号时没有等待。我不确定,但可以通过使用我们用于条件的相同互斥锁保护标志来避免这个问题。因为当设置标志并发出条件信号时,task3
将等待条件或在临界区外工作。
我不知道 task2
是否有问题,例如由于内部问题导致信号丢失,但通常情况下,信号会挂起。
这是我的测试代码。我将 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;
}
一般来说,我建议不要取消或杀死线程。我还尝试尽量减少线程应用程序中的信号处理,或者至少使信号处理程序非常短、非阻塞和简单。最好让线程 运行 有一个循环,例如检查取消标志,或者如果您的线程使用 select 或 epoll 执行 I/O,则让主线程写入管道以指示另一端死亡。对于 C++ 和 pthreads,取消或终止可能更具有灾难性,因此对于 C++,使用自定义代码进行干净关闭更为重要。
参见例如pthread cancel and C++