线程主动结束
线程主动结束,一般就是在线程函数中使用return语句或调用函数pthread_exit。
void pthread_exit(void *retval);
retval是一个指向线程退出状态的指针,这个指针可以被其他线程使用pthread_join获取。在主线程中调用pthread_exit(NULL);的时候,将结束主线程,但进程并不会立即退出。
eg. 线程终止并得到线程退出码
#include <pthread.h>
#include <stdio.h>
#include <malloc.h>
#define PTHREAD_NUM 2
void *thfunc1(void *arg) {
char *count = (char*)malloc(2); // 使用强制转换
if (count == NULL) {
pthread_exit(NULL); // 处理内存分配失败
}
snprintf(count, 2, "1"); // 将字符串复制到分配的内存中
pthread_exit((void*)count); // 返回分配的内存地址
}
void *thfunc2(void *arg) {
int *count = (int*)malloc(sizeof(int)); // 使用强制转换
if (count == NULL) {
pthread_exit(NULL); // 处理内存分配失败
}
*count = 2; // 设置值
return (void*)count; // 返回分配的内存地址 和pthread_exit((void*)count);等价
}
int main(int argc, char *argv[]) {
pthread_t pid[PTHREAD_NUM];
int ret;
char *pRet1;
int *pRet2;
ret = pthread_create(&pid[0], NULL, thfunc1, NULL);
if (ret != 0) {
printf("create pid1 failed\n");
return -1;
}
ret = pthread_create(&pid[1], NULL, thfunc2, NULL);
if (ret != 0) {
printf("create pid2 failed\n");
return -2;
}
if (pid[0] != 0) {
pthread_join(pid[0], (void**)&pRet1);
printf("get pid1 exitcode: %s\n", pRet1);
free(pRet1); // 释放动态内存
}
if (pid[1] != 0) {
pthread_join(pid[1], (void**)&pRet2);
printf("get pid2 exitcode: %d\n", *pRet2);
free(pRet2); // 释放动态内存
}
return 0;
}
线程函数返回字符串和整数数值的时候要注意,必须确定返回值的地址是不变的。如果在栈区开辟变量,那么在函数调用结束的时候,必然是释放内存空间的,就没办法找到count所代表内容的地址空间了。所以使用malloc动态分配内存来存储线程返回的值,并在主线程中释放这些内存。
线程被动结束
线程就被动结束,一种方法是可以在同进程的另外一个线程中通过函数pthread_kill发送信号给要结束的线程,目标线程收到信号后再退出;另外一种方法是在同进程的其他线程中通过函数pthread_cancel来取消目标线程的执行。
int pthread_kill(pthread_t threadId, int signal);
threadId接收信号的线程ID;signal是信号,通常是一个大于0的值,如果等于0,则用来探测线程是否存在。
eg. 向线程发送请求结束信号
#include <pthread.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// 信号处理函数
static void on_signal_term(int sig) {
printf("sub thread will exit\n");
pthread_exit(NULL);
}
void *thfunc(void *arg) {
int tmp = 60;
while (1) {
// 死循环 模拟长时间计算任务
printf("thfunc: %d\n", tmp);
sleep(1);
tmp--;
}
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t tidp;
int ret;
// 注册信号处理函数
signal(SIGQUIT, on_signal_term);
// 创建线程
ret = pthread_create(&tidp, NULL, thfunc, NULL);
if (ret) {
printf("pthread_create failed: %d\n", ret);
return -1;
}
// 让出CPU 5s 让子线程执行
sleep(5);
pthread_kill(tidp, SIGQUIT);
// 等待子线程结束
pthread_join(tidp, NULL);
printf("sub thread has completed, main thread will exit\n");
return 0;
}
信号处理函数被标记为static,确保它只能在当前文件中使用。这样做有助于避免在大型项目中可能出现的命名冲突,并使函数作用域限制在文件内部,从而使代码更加安全和清晰。
eg. 判断线程是否结束
#include <pthread.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <cerrno> // ESRCH
void *thfunc(void *arg) {
int tmp = 60;
while (1) {
// 死循环 模拟长时间计算任务
printf("thfunc: %d\n", tmp);
sleep(1);
tmp--;
}
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t tidp;
int ret;
// 创建线程
ret = pthread_create(&tidp, NULL, thfunc, NULL);
if (ret) {
printf("pthread_create failed: %d\n", ret);
return -1;
}
// 让出CPU 5s 让子线程执行
sleep(5);
ret=pthread_kill(tidp,0);
if (ret == ESRCH) {
printf("the thread did not exit\n");
} else if (ret == EINVAL) {
printf("signal is invalid\n");
} else {
printf("the thread is alive\n");
}
return 0;
}
主线程休眠5秒后,探测子线程是否存活,结果是活着,因为子线程一直在死循环。
如果要让探测结果为子线程不存在了,可以把死循环改为一个可以跳出循环的条件,比如while(tmp>58)。
除了通过函数pthread_kill发送信号来通知线程结束外,还可以通过函数pthread_cancel来取消某个线程的执行。
int pthread_cancel(pthread_t thread);
thread:需要取消的线程的ID。
取消请求的处理并不是立即的,而是依赖于线程在取消点的检查。当线程到达取消点时,它们会检查是否有取消请求,如果有,线程会进行必要的清理工作并终止。如果线程成功取消,它的退出状态会被设置为PTHREAD_CANCELED,这个值通常是 ‒1。通过pthread_join函数可以获取这个退出状态。
常见的取消点在有printf、pthread_testcancel、read/write、sleep等函数调用的地方。
eg. 取消线程失败
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
// 线程函数
void *thfunc(void *arg) {
printf("thread start:\n");
while (1) {
// 线程在忙碌状态下没有检查取消点
}
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t tidp;
int ret;
void *ret2=NULL;
// 创建线程
ret = pthread_create(&tidp, NULL, thfunc, NULL);
if (ret) {
printf("pthread_create failed: %d\n", ret);
return -1;
}
// 让线程运行 1 秒钟
sleep(1);
// 取消线程
ret = pthread_cancel(tidp);
if (ret) {
printf("pthread_cancel failed: %d\n", ret);
return -2;
}
// 等待线程结束
ret = pthread_join(tidp, &ret2);
if (ret) {
printf("pthread_join failed: %d\n", ret);
return -3;
}
// 检查线程退出状态
if (ret2 == PTHREAD_CANCELED) {
printf("Thread has been canceled\n");
} else {
printf("Thread exited with status: %p\n", ret2);
}
return 0;
}
不调用取消点,则无法取消目标线程。只能只能按快捷键Ctrl+C来停止进程,这说明在主线程中虽然发送取消请求了,但并没有让子线程停止运行,因为如果停止运行,pthread_join是会返回并打印其后面的语句的。
void pthread_testcancel(void);
pthread_testcancel函数可以在线程的死循环中让系统(内核)有机会去检查是否有取消请求过来。
eg. 取消线程成功
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
// 线程函数
void *thfunc(void *arg) {
printf("thread start:\n");
while (1) {
// 取消点,允许线程响应取消请求
pthread_testcancel();
}
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t tidp;
int ret;
void *ret2;
// 创建线程
ret = pthread_create(&tidp, NULL, thfunc, NULL);
if (ret) {
printf("pthread_create failed: %d\n", ret);
return -1;
}
// 让线程运行 1 秒钟
sleep(1);
// 取消线程
ret = pthread_cancel(tidp);
if (ret) {
printf("pthread_cancel failed: %d\n", ret);
return -2;
}
// 等待线程结束
ret = pthread_join(tidp, &ret2);
if (ret) {
printf("pthread_join failed: %d\n", ret);
return -3;
}
// 检查线程退出状态
if (ret2 == PTHREAD_CANCELED) {
printf("Thread has been canceled\n");
} else {
printf("Thread exited with status: %p\n", ret2);
}
return 0;
}
如果不用pthread_testcancel,则可以在while循环中用sleep函数来代替,但这样会影响while的速度,在实际开发中,可以根据具体项目具体分析。