线程分离、线程退出和线程清理

一、线程分离

线程分离是指将一个线程从主线程中分离出来,使其能够独立运行,并且不再需要其他线程使用pthread_join()函数来等待其结束。

当一个线程被设置为分离状态后,它结束时,系统会自动回收其资源,而不需要其他线程进行显式的资源回收操作。

线程在Linux系统中有两种状态:结合态(joinable)和分离态(detached)。默认情况下,线程被创建为结合态。

  • 在结合态下,当线程函数执行完毕或调用pthread_exit退出时,线程所占用的堆栈和线程描述符等资源不会立即释放。只有当其他线程调用pthread_join函数等待该线程结束时,这些资源才会被释放。这种状态适用于需要等待线程完成并获取其返回值的情况。
  • 而在分离态下,当线程函数执行完毕或调用pthread_exit退出时,线程所占用的资源会自动释放,无需其他线程进行显式的资源回收操作。要将线程设置为分离态,可以使用pthread_detach函数。这种状态适用于不需要等待线程完成或获取其返回值的情况,可以提高程序的效率和资源利用率。

 创建一个线程后应回收其资源,但使用pthread_join函数会使调用者阻塞,故Linux提供了 线程分离函数:pthread_detach

 #include <pthread.h>
 int pthread_detach(pthread_t thread);

  功能:
    使调用线程与当前进程分离,使其成为一个独立的线程,
    该线程终止时,系统将自动回收它的资源。
 参数:
     thread:指定的子线程的id
 返回值:
     成功:0
     失败:非0

案例 

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

void *thread_fun(void *arg)
{
    printf("子线程正在运行\n");

    sleep(3);

    printf("子线程要退出了\n");
}

int main(int argc, char const *argv[])
{
    printf("主控线程正在执行\n");

    pthread_t thread;

    if(pthread_create(&thread, NULL, thread_fun, NULL) != 0)
    {
        perror("fail to pthread_create");
        exit(1);
    }

    //通过pthread_detach函数将子线程设置为分离态,既不用阻塞,也可以自动回收子线程退出的资源
    if(pthread_detach(thread) != 0)
    {
        perror("fail to pthread_detach");
        exit(1);
    }

#if 0
    //如果原本子线程是结合态,需要通过pthrad_join函数回收子线程退出的资源,
    //但是这个函数是一个阻塞函数,如果子线程不退出,就会导致当前进程(主控线程)无法继续执行,大     大的限制了代码的运行效率
    //如果子线程已经设置为分离态,就不需要再使用pthread_join了
    if(pthread_join(thread, NULL) != 0)
    {
        perror("fail to pthread_join");
        exit(1);
    }
#endif

    while(1)
    {
        printf("hello world\n");
        sleep(1);
    }
    return 0;
}

二、线程退出 

1、线程退出函数

 #include <pthread.h>
  void pthread_exit(void *retval);
  功能:
    退出正在执行的线程
  参数:
    retval:当前线程的退出状态值,
    这个值可以被调用pthread_join函数的线程接收到
返回值:
    无

注: 一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并 不会释放。如果要释放资源,结合态需要通过pthread_join函数,分离态则自动释放

案例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

void *thread_fun(void *arg)
{
   printf("子线程正在运行\n");

   static char buf[] = "This thread has quited";

   int i;
   for(int i = 0; i < 10; i++)
   {
       if(i == 5)
       {
           //通过pthread_exit函数退出当前线程
           //pthread_exit(NULL);
           pthread_exit(buf);
       }
       printf("*******************\n");
       sleep(1);
   }
}

int main(int argc, char const *argv[])
{
   printf("主控线程正在执行\n");

   pthread_t thread;

   if(pthread_create(&thread, NULL, thread_fun, NULL) != 0)
   {
       perror("fail to pthread_create");
       exit(1);
   }

   //pthread_join(thread, NULL);
   char *str;
   pthread_join(thread, (void **)&str);
   printf("str = %s\n", str);
   printf("进程要退出了\n");
   return 0;
}
  • 在这个代码中,子线程在循环到第5次时,调用了pthread_exit函数来退出线程,并将一个字符串指针作为返回值传递给主线程。在主线程中,通过pthread_join函数来等待子线程的结束,并将子线程的返回值存储在指针str中。最后,主线程打印出这个字符串,并退出整个进程。需要注意的是,在使用pthread_join函数时,需要将第二个参数的类型转换为void **,以正确地获取子线程的返回值。

 执行结果

2.线程的取消 

2.1 pthread_cancel()

  #include <pthread.h>
  int pthread_cancel(pthread_t thread);
 功能:
    取消线程
 参数:
    thread:要销毁的线程的id
 返回值:
    成功:0
    失败:非0

pthread_cancel函数的实质是发信号给目标线程thread,使目标线程退出。

此函数只是发送终止信号给目标线程,不会等待取消目标线程执行完才返回。 然而发送成功并不意味着目标线程一定就会终止,线程被取消时,线程的取消属性会决 定线程能否被取消以及何时被取消。

线程的取消状态

即线程能不能被取消 

线程取消点

即线程被取消的地方

线程的取消类型

在线程能被取消的状态下,是立马被取消结束还是执行到取消点的时候被取消结束

案例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

void *pthread_fun(void *arg)
{
    while(1)
    {
        printf("子线程正在运行\n");

        sleep(1);
    }
}

int main(int argc, char const *argv[])
{
    pthread_t thread;

    if(pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
    {
        perror("fail to pthread_create");
    }

    // 等待3秒后,通过调用pthread_cancel函数取消子线程
    sleep(3);
    pthread_cancel(thread);

    // 使用pthread_join函数等待子线程结束,并回收资源
    pthread_join(thread, NULL);

    return 0;
}

 执行结果

 2.2pthread_setcancelstate()

线程的取消状态

在Linux系统下,线程默认可以被取消。编程时可以通过pthread_setcancelstate函数 设置线程是否可以被取消。

 #include <pthread.h>
  int pthread_setcancelstate(int state, int *oldstate);
功能:
    设置线程是否被取消
参数:
    state:新的状态
    PTHREAD_CANCEL_DISABLE:不可以被取消
    PTHREAD_CANCEL_ENABLE:可以被取消
    oldstate:保存调用线程原来的可取消状态的内存地址
返回值:
    成功:0
    失败:非0

案例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

void *pthread_fun(void *arg)
{
    // 通过pthread_setcancelstate设置取消的状态
    // 设置为可以取消
    // pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    // 设置为不可取消
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

    while(1)
    {
        printf("子线程正在运行\n");

        sleep(1);
    }
}

int main(int argc, char const *argv[])
{
    pthread_t thread;

    if(pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
    {
        perror("fail to pthread_create");
    }

    sleep(3);
    // 调用pthread_cancel函数取消子线程
    pthread_cancel(thread);

    // 使用pthread_join函数等待子线程结束并回收资源
    pthread_join(thread, NULL);

    return 0;
}

 执行结果

 2.3 pthread_setcanceltype()

线程的取消类型

线程被取消后,该线程并不是马上终止,默认情况下线程执行到消点时才能被终止。

编 程时可以通过pthread_setcanceltype函数设置线程是否可以立即被取消。

 #include <pthread.h>
  int pthread_setcanceltype(int type, int *oldtype);
功能:
     设置线程是否可以立即被取消
参数:
     type:类型
     PTHREAD_CANCEL_ASYNCHRONOUS:立即取消、
     PTHREAD_CANCEL_DEFERRED:不立即被取消
     oldtype:保存调用线程原来的可取消类型的内存地址
返回值:
     成功:0
     失败:非0

案例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

void *pthread_fun(void *arg)
{
   pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

   // 设置线程取消的类型
   // 设置为立即取消
   // pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
   // 设置为不立即取消
   pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);

   while(1)
   {
       printf("子线程正在运行\n");

       sleep(1);
   }
}

int main(int argc, char const *argv[])
{
   pthread_t thread;

   if(pthread_create(&thread, NULL, pthread_fun, NULL) != 0)
   {
       perror("fail to pthread_create");
   }

   sleep(3);
   // 调用pthread_cancel函数取消子线程
   pthread_cancel(thread);

   // 使用pthread_join函数等待子线程结束并回收资源
   pthread_join(thread, NULL);

   return 0;
}
  • 这个代码中,子线程通过调用pthread_setcancelstate函数将自身的取消状态设置为PTHREAD_CANCEL_ENABLE,即可以取消状态。然后,子线程通过调用pthread_setcanceltype函数将自身的取消类型设置为PTHREAD_CANCEL_DEFERRED,即延迟取消类型。这意味着子线程只有在到达取消点时才会响应取消请求。
  • 在主线程中,我们等待3秒后调用pthread_cancel函数来尝试取消子线程。由于子线程的取消类型是延迟取消,所以取消请求不会立即生效,而是在子线程到达下一个取消点时才会被处理。最后,我们使用pthread_join函数等待子线程结束并回收资源。

 执行结果

3、线程退出清理函数

和进程的退出清理一样,线程也可以注册它退出时要调用的函数,这样的函数称为线程 清理处理程序(thread cleanup handler)。

注意:

线程可以建立多个清理处理程序。

处理程序在栈中,故它们的执行顺序与它们注册时的顺序相反。

当线程执行以下动作时会调用清理函数:

1、调用pthread_exit退出线程。

2、响应其它线程的取消请求。

3、用非零execute调用pthread_cleanup_pop。

3.1 pthread_cleanup_push()

 #include <pthread.h>
  void pthread_cleanup_push(void (* routine)(void *), void *arg);
  功能:
    将清除函数压栈。即注册清理函数。
 参数:
    routine:线程清理函数的指针。 
    arg:传给线程清理函数的参数。
返回值:无

3.2 pthread_cleanup_pop()

  #include <pthread.h>
  void pthread_cleanup_pop(int execute);
功能:
    将清除函数弹栈,即删除清理函数。
参数:
    execute:线程清理函数执行标志位。
    非0,弹出清理函数,执行清理函数。
    0,弹出清理函数,不执行清理函数。
返回值:
    无

3.3案例:验证线程调用pthread_exit函数时,系统自动调用线程清理函数

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

void mycleanup(void *arg)
{
   printf("clean up ptr = %s\n", (char *)arg);
   free((char *)arg);
}

void *thread(void *arg)
{
   /*建立线程清理程序*/ 
   printf("this is new thread\n");

   char *ptr = NULL;
   ptr = (char*)malloc(100);
   pthread_cleanup_push(mycleanup, (void*)(ptr)); 
   bzero(ptr, 100);
   strcpy(ptr, "memory from malloc");
   
   sleep(3);
   printf("before exit\n");

   pthread_exit(NULL);
   
   /*注意push与pop必须配对使用,即使pop执行不到*/
   printf("before pop\n");
   pthread_cleanup_pop(1);
}

int main(int argc, char *argv[])
{
   pthread_t tid;
   pthread_create(&tid, NULL, thread, NULL);  // 创建一个线程
   pthread_join(tid,NULL);
   printf("process is dying\n");
   return 0;
}

在这个代码中,我们定义了一个名为mycleanup的函数,它接受一个指针参数并打印出该指针指向的字符串,然后使用free函数释放该指针指向的内存。

thread函数中,我们首先打印一条消息以指示新线程的开始。然后,我们使用malloc函数分配一块100字节的内存,并将指向该内存的指针存储在ptr变量中。接下来,我们使用pthread_cleanup_push函数将mycleanup函数注册为线程的清理程序,并将ptr指针作为参数传递给它。这意味着当线程退出时,mycleanup函数将被自动调用,以释放ptr指针指向的内存。

接下来,我们使用bzero函数将分配的内存初始化为零,并使用strcpy函数将字符串"memory from malloc"复制到该内存中。然后,我们使用sleep函数使线程睡眠3秒,并打印一条消息以指示线程即将退出。

在线程退出之前,我们调用pthread_exit函数以正常方式退出线程。这将导致已注册的清理程序mycleanup被自动调用,以释放之前分配的内存。

在主函数中,我们使用pthread_create函数创建一个新线程,并将thread函数作为线程的入口点。然后,我们使用pthread_join函数等待线程完成,并打印一条消息以指示进程即将退出。

执行结果

3.4案例:验证调用pthread_cleanup_pop函数时,系统自动调用线程清理函数 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

void cleanup_func1(void *arg)
{
    printf("in cleanup func1\n");
    printf("clean up ptr = %s\n", (char *)arg);
    free((char *)arg);
}

void cleanup_func2(void *arg)
{
    printf("in cleanup func2\n");
}

void *thread(void *arg)
{
    char *ptr = NULL;
    
    /*建立线程清理程序*/ 
    printf("this is new thread\n");
    ptr = (char*)malloc(100);
    pthread_cleanup_push(cleanup_func1, (void*)(ptr)); 
    pthread_cleanup_push(cleanup_func2, NULL); 
    bzero(ptr, 100);
    strcpy(ptr, "memory from malloc");
    
    /*注意push与pop必须配对使用,即使pop执行不到*/
    sleep(3);
    printf("before pop\n");
    pthread_cleanup_pop(1); // 执行cleanup_func2
    printf("before pop\n");
    pthread_cleanup_pop(1); // 执行cleanup_func1
    
    return NULL;
}

int main(int argc, char *argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, thread, NULL);  // 创建一个线程
    pthread_join(tid,NULL);
    printf("process is dying\n");
    return 0;
}

在这个代码中,我们定义了两个清理函数:cleanup_func1cleanup_func2cleanup_func1接受一个指针参数,打印出该指针指向的字符串,并使用free函数释放该指针指向的内存。cleanup_func2则只打印一条消息。

thread函数中,我们首先打印一条消息以指示新线程的开始。然后,我们使用malloc函数分配一块100字节的内存,并将指向该内存的指针存储在ptr变量中。接下来,我们使用pthread_cleanup_push函数将cleanup_func1cleanup_func2注册为线程的清理程序。我们将ptr指针作为参数传递给cleanup_func1,并将NULL作为参数传递给cleanup_func2。这意味着当线程退出时,这两个函数将按照与它们被注册的相反顺序被自动调用。

接下来,我们使用bzero函数将分配的内存初始化为零,并使用strcpy函数将字符串"memory from malloc"复制到该内存中。然后,我们使用sleep函数使线程睡眠3秒,并打印两条消息。

在线程退出之前,我们使用pthread_cleanup_pop函数以与它们被注册的相反顺序弹出已注册的清理程序。第一个pthread_cleanup_pop函数将导致cleanup_func2被调用,而第二个pthread_cleanup_pop函数将导致cleanup_func1被调用。这将释放之前分配的内存,并打印相应的消息。

在主函数中,我们使用pthread_create函数创建一个新线程,并将thread函数作为线程的入口点。然后,我们使用pthread_join函数等待线程完成,并打印一条消息以指示进程即将退出。

执行结果

  • 33
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要知道进程和线程是否已经退出,可以通过以下几种方式来判断: 1. 进程退出:可以使用进程等待的方式来获取子进程的退出信息。父进程可以调用wait()或waitpid()函数来等待子进程的退出,并获取子进程的退出状态。这样可以确保父进程能够管理子进程的运行结果和资源回收。等待子进程退出还可以防止僵尸进程的产生,避免内存泄漏等问题。\[1\] 2. 线程退出线程退出可以通过线程的返回值来判断。线程函数可以通过返回一个值来表示线程的执行结果。父线程可以通过调用pthread_join()函数来等待子线程退出,并获取子线程的返回值。这样可以确保父线程能够获取子线程的执行结果。\[1\] 3. 孤儿进程和僵尸进程的区分:孤儿进程是指失去父进程的子进程,而僵尸进程是指子进程已经退出但父进程还没有对其进行资源回收的进程。可以通过查看进程的父进程ID来判断进程是孤儿进程还是僵尸进程。如果一个进程的父进程已经先结束了,那么该进程就不会变成僵尸进程,因为系统会由Init进程来接管它,成为它的父进程。\[2\]\[3\] 综上所述,通过进程等待和线程返回值的方式,可以判断进程和线程是否已经退出。此外,通过查看进程的父进程ID可以区分孤儿进程和僵尸进程。 #### 引用[.reference_title] - *1* [Linux系统编程——进程(四)进程的退出,子进程退出的信息收集,以及僵尸进程和孤儿进程](https://blog.csdn.net/qq_48458789/article/details/116451653)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [进程和线程退出](https://blog.csdn.net/weixin_51281362/article/details/125513350)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值