四、分离属性。
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的参数,那么也会执行一遍并删除。