线程的结束(Linux C/C++笔记)

线程主动结束

线程主动结束,一般就是在线程函数中使用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的速度,在实际开发中,可以根据具体项目具体分析。

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值