分离属性,线程取消例程函数,线程响应取消行为的类型

本文详细介绍了线程的分离属性,如何创建分离属性线程,以及线程取消的概念和类型。通过示例代码展示了线程响应取消的行为,包括延迟取消和立即取消,并解释了线程取消例程函数的作用和使用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

四、分离属性。
1、什么是分离属性?
首先分离属性是线程的一个属性,有了分离属性的线程,不需要别的线程去接合自己。(自己会回收自己的资源)
但是虽然说是分离,但是进程退出了,该线程还是要退出的。

分离属性线程   -> 不需要pthread_join()去接合线程,即使调用也会返回。
非分离属性线程 -> 需要pthread_join()去接合线程   -> 默认创建的普通属性就是非分离属性。

2、如何创建出分离属性的线程?
方法一:添加一个分离属性到一个属性变量,然后使用属性变量去创建一个子线程,那么创建出来的子线程就是具有分离属性的线程。

1)定义一个属性变量  -> 数据类型: pthread_attr_t
   pthread_attr_t attr;

2)初始化属性变量。   -> pthread_attr_init()  -> man 3 pthread_attr_init
功能:initialize thread attributes object
头文件:
    #include <pthread.h>

原型:
       int pthread_attr_init(pthread_attr_t *attr);

参数:
    attr: 需要初始化的属性变量的地址。

返回值:
    成功:0
    失败:非0

3)添加分离属性到属性变量中。  -> pthread_attr_setdetachstate()  -> man 3 pthread_attr_setdetachstate
头文件:
    #include <pthread.h>

原型:
       int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

参数:
    attr:需要添加属性的属性变量的地址
    detachstate:
        PTHREAD_CREATE_DETACHED  -> 分离属性
        PTHREAD_CREATE_JOINABLE  -> 非分离属性
    
返回值:
    成功:0
    失败:非0

4)利用分离属性去创建子线程。
   pthread_create(&tid,&attr,xxxx);

5)那么创建出来的线程是分离属性的线程,在线程结束时,会自动回收自己的资源。

6)销毁属性变量。  -> pthread_attr_destroy()  -> man 3 pthread_attr_destroy
头文件:
    #include <pthread.h>
原型:
       int pthread_attr_destroy(pthread_attr_t *attr);

参数:
    attr:需要销毁的属性变量的地址

返回值:
    成功:0
    失败:非0


   练习1: 验证一个分离属性的线程退出时,主线程如果还接合它,接合函数会阻塞吗?
   练习2: 验证一个分离属性的线程工作时间比较长,主线程工作时间短,主线程工作完导致进程退出,子线程会退出吗?

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

//子线程正常做任务,正常退出即可。
void *my_fun(void *arg)  //工作5S,然后自己退出,自己回收自己的资源
{
    int i;
    for(i=0;i<5;i++)
    {
        printf("%d\n",i);
        sleep(1);
    }
}

int main(int argc,char *argv[])
{
    //1、 定义一个属性变量。
    pthread_attr_t attr;
    
    //2、 初始化属性变量。
    pthread_attr_init(&attr);
    
    //3、 添加分离属性到属性变量中。
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    
    //4、 使用该属性变量去创建一条线程。
    pthread_t tid;
    pthread_create(&tid,&attr,my_fun,NULL);
    
    //5、 故意还去接合线程
    int ret;
    ret = pthread_join(tid,NULL);
    if(ret == 0)
    {
        printf("pthread join success!\n");
    }    
    else{
        printf("pthread join error!\n");
    }
    
    //6、 睡眠2S
    sleep(2);
    
    return 0; //进程退出了,所有的线程(不管是不是分离的)都会退出。
}

结果:
pthread join error!  -> 一运行pthread_join(),函数就会马上返回失败!
0
1   -> 2S后,进程退出,会导致分离属性的线程也会退出。


方法二:先创建一条普通属性的线程,在子线程调用设置自己为分离属性的函数即可。
1)设置线程本身属性为分离属性。   ->  pthread_detach()  -> man 3 pthread_detach
功能: detach a thread
    //设置分离属性给线程。

头文件:
    #include <pthread.h>
    
原型:
    int pthread_detach(pthread_t thread);

参数:
    thread: 需要设置分离属性的线程。

返回值:
    成功:0
    失败:非0

2)获取线程的ID号。  -> pthread_self()   -> man 3 pthread_self
功能: obtain ID of the calling thread
    //获取一个正在运行的线程的ID号。

头文件:
    #include <pthread.h>

原型:
    pthread_t pthread_self(void);

返回值:线程的ID号。

   练习3:使用方法二设置线程分离属性。

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

//子线程正常做任务,正常退出即可。
void *my_fun(void *arg)  //工作5S,然后自己退出,自己回收自己的资源
{
    //目前为止,我还是非分离的线程
    //3. 设置分离属性给自己。
    int ret;
    ret = pthread_detach(pthread_self());
    printf("ret = %d\n",ret);
    
    int i;
    for(i=0;i<5;i++)
    {
        printf("%d\n",i);
        sleep(1);
    }
}

int main(int argc,char *argv[])
{
    //1、 使用普通属性去创建一条线程。
    pthread_t tid;
    pthread_create(&tid,NULL,my_fun,NULL);
    
    //2、 睡眠2S
    sleep(2);
    
    //4、 故意还去接合线程
    int ret;
    ret = pthread_join(tid,NULL);  //主线程join的时候已经知道线程是分离属性的了,所以返回失败
    if(ret == 0)
    {
        printf("pthread join success!\n");
    }    
    else{
        printf("pthread join error!\n");
    }
    
    return 0; //进程退出了,所有的线程(不管是不是分离的)都会退出。
}

五、 线程的取消。
1、一般主线程不用于处理任务,只是控制子线程状态,例如取消,接合   -> pthread_cancel()  -> man 3 pthread_cancel
   主线程  -> 发送取消请求  ->  子线程  -> 收到取消请求  -> 子线程就会退出。

功能: send a cancellation request to a thread
    //发送一个取消请求给线程

头文件:
    #include <pthread.h>

原型:
       int pthread_cancel(pthread_t thread);

参数:
    thread: 需要收到取消请求的线程ID号。

返回值:
    成功:0
    失败:非0

补充: 默认普通属性的线程可以响应取消请求。

2、 关于线程取消案例。
    尝试让主线程发送一个取消请求给子线程,看看子线程会不会被取消掉。
    
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

//子线程负责工作即可。
void *fun(void *arg)
{
    //线程默认情况: 非分离   能响应取消   延迟取消

    int i;
    for(i=0;i<5;i++)
    {
        printf("%d\n",i);
        sleep(1);
    }
}

int main(int argc,char *argv[])// 看看小孩还会继续工作?
{
    //1. 创建一个子线程
    pthread_t tid;
    pthread_create(&tid,NULL,fun,NULL);
    
    //2. 等待几秒,让孩子工作几秒
    sleep(2);
    
    //3. 发送一个取消请求给小孩
    pthread_cancel(tid);
    
    //4. 小孩退出了,需要去接合线程
    pthread_join(tid,NULL);
    
    return 0;
}

结果:
gec@ubuntu:/mnt/hgfs/GZ2057/09 系统编程/06/code$ ./cancel_test
0
1

子线程只工作了2s ,就退出。

六、研究线程响应取消的状态。   -> pthread_setcancelstate() -> man 3 pthread_setcancelstate
默认情况,线程都是能响应取消。
如果线程设置了不能响应取消的请求,那么如果将收到了取消的请求,也不会被取消。

1、 函数功能介绍:
功能: set cancelability state
    //设置线程的取消状态

头文件:
    #include <pthread.h>

原型:
    int pthread_setcancelstate(int state, int *oldstate);

参数:
    state:
        PTHREAD_CANCEL_ENABLE   -> 能响应  -> 线程默认属性
        PTHREAD_CANCEL_DISABLE  -> 不能响应
    
    oldstate: 保留之前的状态,如果不关心,则填NULL。

返回值:
    成功:0
    失败:非0

2、 案例:
    创建一个子线程,然后设置不能响应取消行为给线程,然后再发送一个取消请求给线程,看看线程会不会提前退出。

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

//子线程负责工作即可。
void *fun(void *arg)
{
    //1. 默认情况下,都是能响应。
    //2. 设置自己为不能响应取消。
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
    
    int i;
    for(i=0;i<5;i++)
    {
        printf("%d\n",i);
        sleep(1);
    }
}

int main(int argc,char *argv[])// 看看小孩还会继续工作?
{
    //1. 创建一个子线程
    pthread_t tid;
    pthread_create(&tid,NULL,fun,NULL);
    
    //2. 等待几秒,让孩子工作几秒
    sleep(2);
    
    //3. 发送一个取消请求给小孩
    pthread_cancel(tid);
    printf("I send cancel to you!\n");
    
    //4. 小孩退出了,需要去接合线程
    pthread_join(tid,NULL);
    
    return 0;
}

结果:
gec@ubuntu:/mnt/hgfs/GZ2057/09 系统编程/06/code$ ./cancel_test
0
1
I send cancel to you!
2
3
4

小孩能够正常工作5S,不能被取消。

    练习4: 验证以下的结论是对的:
    If a cancellation request is received, it is blocked until cancelability is enabled.
    //在不能响应取消请求的情况下,如果收到了一个取消请求,那么这个取消请求会阻塞到能响应取消为止。


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

void *fun(void *arg)
{
    //1. 设置不能响应
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);

    int i;
    for(i=10;i>0;i--)
    {
        printf("disable cancel!\n");
        sleep(1);
    } //主线程给自己发取消请求时,我是处于一个不能响应取消行为的状态
    
    //2. 设置为能响应。
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);   -> 只要设置能响应,就会马上响应。
    
    for(i=10;i>0;i--)
    {
        printf("enable cancel!\n");
        sleep(1);
    }
}

int main(int argc,char *argv[])
{
    //1. 创建普通属性线程
    pthread_t tid;
    pthread_create(&tid,NULL,fun,NULL);
    
    //2. 睡眠2S后,发送取消请求子线程
    sleep(2);
    pthread_cancel(tid);
    
    //3. 接合线程
    pthread_join(tid,NULL);
    
    return 0;
}

七、线程响应取消行为的类型。   ->  pthread_setcanceltype()  -> man 3 pthread_setcanceltype

1、 函数功能介绍:
状态:  enable:能响应,disable:不能响应。
类型:  基于能响应的情况,又分为立即取消和延迟取消。
    
功能:set cancelability type
    //设置取消类型。

头文件:
    #include <pthread.h>

原型:
    int pthread_setcanceltype(int type, int *oldtype);

参数:
    type:
    PTHREAD_CANCEL_DEFERRED      -> 延迟取消  
                                     -> 收到取消请求后,不能立即响应,而是遇到取消取消点函数才会响应
                     -> 线程默认属性就是延迟取消。

    PTHREAD_CANCEL_ASYNCHRONOUS  -> 立即取消
    oldtype: 保留之前的状态,如果不关心,则填NULL。

返回值:    
    成功:0
    失败:非0

取消点函数有哪些?  -> man 7 pthreads
usleep()
sleep()
fprintf()
fputc()
printf()

2、 写一个明显的延迟取消案例。
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void *fun(void *arg)
{
    //默认: 能响应  延迟取消
    //0. 早就收到取消。
    
    //1. 想办法让线程不要遇到取消点函数。
    int i,j;
    for(i=0;i<100000;i++)
    {
        for(j=0;j<100000;j++)
        {
            
        }  //持续一段时间
    }
    
    //2. 故意遇到取消点。
    while(1)
    {
        fputc('a',stderr); //把'a'放在标准出错的缓冲区  fputc是取消点函数,所以程序运行到这里,就会响应取消。
        printf("helloworld!\n");
    }
}

int main(int argc,char *argv[])
{
    //1. 创建子线程。
    pthread_t tid;
    pthread_create(&tid,NULL,fun,NULL);
    
    //2. 发送一个取消请求给子线程。
    pthread_cancel(tid);
    printf("I send cancel to thread!\n");
    
    //3. 接合线程。
    pthread_join(tid,NULL);
    
    return 0;
}

八、线程取消例程函数。
1、什么是线程取消例程函数?
当线程收到取消请求时,先不要马上响应取消请求,而是执行一个例程函数先,执行完之后,再响应取消。

2、 为什么要使用取消例程函数?
为了防止线程带着一些公共资源时被取消掉。
如果你带着资源退出,那么其他的线程就无法再次使用该资源。

3、 如何实现?
1)在线程内部说明例程函数是哪个?   -> pthread_cleanup_push()   -> man 3 pthread_cleanup_push
功能: push thread cancellation clean-up handlers
头文件:
    #include <pthread.h>

原型:
    void pthread_cleanup_push(void (*routine)(void *),void *arg);

参数:
    routine:线程取消例程函数   -> 必须是: void fun(void *arg)
    arg:传递给取消例程函数的参数,如果不需要,则填NULL。

返回值:无。

回顾学习过的例程函数:
信号处理函数: void  fun(  int sig)
线程例程函数: void* fun(void* arg)
取消例程函数: void  fun(void* arg)

2)删除例程函数   -> pthread_cleanup_pop()   -> man 3 pthread_cleanup_pop
功能: pop thread cancellation clean-up handlers
头文件:
    #include <pthread.h>

原型:
    void pthread_cleanup_pop(int execute);

参数:
    execute: 非0  -> 删除时,先执行一遍函数,再删除。
           0   -> 直接删除。

返回值:无

模型:
//取消例程函数
void cancel_fun(void *arg)
{

}

子线程:
void *thread_func(void *arg)
{
    //将来我收到取消请求,先执行cancel_fun函数,再响应取消。
    pthread_cleanup_push(cancel_fun,NULL);

    .....;    
    .....;
    .....;  <--- 收到了取消请求
    .....;

    //删除取消例程函数
    pthread_cleanup_pop(0);
}

基于以上模型,有以下几种情况:
1)线程运行后,收到了取消请求。
   执行cancel_fun这个函数   -> 响应取消 -> 线程提前退出
(已经帮你删除掉cancel_fun了,所以你的程序无法执行到后面的删除函数。所以后面的删除函数,无论参数是0还是非0,都不会再次执行 cancel_fun)    
                      
2)线程运行后,一直都没有收到取消请求。
   删除时: pthread_cleanup_pop(0);  -> 参数是0   -> 直接删除。

3)线程运行后,一直都没有收到取消请求。
   删除时: pthread_cleanup_pop(1);  -> 参数是非0  -> 先执行一遍cancel_fun()  -> 再删除。

  练习5: 子线程收到主线程给自己发送的取消请求时,不要马上取消,而是先打印一句话"I recv cancel",再响应取消请求。
      顺便使用这个练习,验证以上的三种情况。

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

void func(void *arg)
{
    printf("I recv cancel\n");
}

void *my_fun(void *arg)
{
    //1. 将来收到取消请求,先执行线程取消例程函数。
    pthread_cleanup_push(func,NULL);
    
    //2. 子线程开始工作。
    int i;
    for(i=0;i<10;i++)
    {
        printf("helloworld!\n");
        sleep(1);
    }
    
    //3. 删除函数。
    pthread_cleanup_pop(2); //删除时,不会执行。
}

int main(int argc,char *argv[])
{
    //1. 创建一个线程。
    pthread_t tid;
    pthread_create(&tid,NULL,my_fun,NULL);
    
    //2. 3S后,发送一个取消请求给子线程。
    //sleep(3);
    //pthread_cancel(tid);
    //printf("I send cancel to child thread!\n");
    
    //3. 接合线程
    pthread_join(tid,NULL);
    
    return 0;
}


A cancellation clean-up handler is popped from the stack and executed in the following circumstances:
//线程取消例程函数被删除并且会被执行以下几种情况:

1. When  a  thread  is canceled, all of the stacked clean-up handlers are popped and executed in the reverse of the order in which they were pushed onto the stack.
//当你收到了一个取消请求时,会执行一遍例程函数,并删除。

2. When a thread terminates by calling pthread_exit(3), all clean-up handlers are executed as described in  the  preceding  point.
如果线程因为调用pthread_exit()函数而退出时,会执行一遍例程函数,并删除。

(Clean-up handlers are not called if the thread terminates by performing a return from the thread start function.)
如果线程因为执行return语句而退出,则例程函数不会执行,但是会删除。


3. When  a  thread  calls  pthread_cleanup_pop() with a nonzero execute argument, the top-most clean-up handler is popped and executed.
//当线程执行pthread_cleanup_pop()去删除线程例程函数时,携带了一个非0的参数,那么也会执行一遍并删除。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值