目录
1、线程分离属性
默认情况下,线程启动后处于可接合状态(即未分离),此时的线程可以在退出时让其他线程接合以便释放资源,但若其他线程未及时调用 pthread_join()
去接合它,它将成为僵尸线程,浪费系统资源。
因此,若线程退出时无需汇报其退出值,则一般要设置为分离状态,处于分离状态下的线程在退出之后,会自动释放其占用的系统资源。
将线程设置为分离状态有两种方式:
- 在线程启动前,使用分离属性启动线程
- 在线程启动后,使用
pthread_detach()
强制分离
线程分离方法一
方法1:添加一个分离属性到一个属性变量中,然后使用属性变量去创建一个线程, 那么创建出来的线程就是具有分离属性的线程
1)定义一个属性变量 -> 数据类型:pthread_attr_t
pthread_attr_t attr;
2)初始化属性变量。 -> pthread_attr_init() -> man 3 pthread_attr_init
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
参数: attr:未初始化的属性变量
返回值: 成功:0 失败:非0错误码
3)设置分离属性到属性变量中。
#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,.....); -> 创建出来的线程就是分离属性的线程,不需要pthread_join()
5)销毁属性变量。 -> pthread_attr_destroy() -> man 3 pthread_attr_destroy int pthread_attr_destroy(pthread_attr_t *attr);
参数: attr:已经初始化过的属性变量
返回值: 成功:0 失败:非0错误码
线程分离方法二
方法2:
先创建一个普通线程,然后在线程中调用一个设置分离属性的函数,那么这个线程就变成分离的属性
1)设置线程本身的属性为分离属性。 -> pthread_detach() -> man 3 pthread_detach #include <pthread.h>
int pthread_detach(pthread_t thread);
函数作用: 设置分离属性给线程
参数: thread:需要设置分离属性的线程的ID号
返回值: 成功:0 失败:非0错误码
2)获取线程的ID号。 -> pthread_self() -> man 3 pthread_self
#include <pthread.h> pthread_t pthread_self(void);
参数: 无
返回值:线程的ID号
pthread_detach(pthread_self())
总结: 无论是否添加了分离属性的线程,理论上创建的线程的数量没有限制。 用两种方法设置分离属性的子线程,主线程都无法结合成功
测试代码如下:
#include<stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
void* start_routine(void *arg)
{
//pthread_detach(pthread_self());
printf("[%lu]start_routine\n",pthread_self()); //pthread_self打印当前线程的ID号
sleep(3);
}
int main()
{
//方法一
//1、定义一个 线程属性变量
pthread_attr_t attr;
//2、初始化清空 属性变量
pthread_attr_init(&attr);
//3、把分离属性 加入 到 属性变量中
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
//4、创建一条具有分离属性的子线程
pthread_t thread;
pthread_create(&thread,&attr,start_routine,NULL);
//方法二
//pthread_create(&thread,NULL,start_routine,NULL);
//5、销毁属性变量
pthread_attr_destroy(&attr);
//阻塞等待子线程退出,回收资源
int ret = pthread_join(thread,NULL);
if(ret != 0)
{
printf("pthread_join error\n");
}
}
2、线程取消(状态,类型)
一般主线程不用于去处理任务,只是控制子线程状态,例如取消接合
主线程 ---> 取消请求 ---> 子线程 pthread_cancel() ---> man 3 pthread_cancel #include<pthread.h>
int pthread_cancel(pthread_t thread);
函数作用: 发送一个取消请求给子线程。
参数: thread:需要取消的线程的ID号。
返回值: 成功:0 失败:非0错误码
注意:线程收到取消请求,就等价于提前退出
pthread_exit() -> 是线程主动退出 ->退出值-> pthread_join()
主线程给子线程发送取消请求--->子线程收到取消请求-> 线程被迫退出(被动退出) -> 没有退出值
pthread_setcancelstate() -> man 3 pthread_setcancelstate
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
参数:
state: PTHREAD_CANCEL_ENABLE -> 能响应 -> 线程默认属性 -> 是马上响应,还是延迟响应 -> 取决于type。
PTHREAD_CANCEL_DISABLE -> 不能响应
oldstate:保留之前的状态,如果不关心,则填NULL。
返回值: 成功:0 失败:非0错误码。
说明: pthread_setcancelstate一般使用在子线程中
If a thread has disabled cancellation, then a cancellation request remains queued until the thread enables cancellation.
//如果一个线程不能响应取消的,那么在这个过程中收到了取消请求,那么这个请求会直到这个线程能响应取消请求为止才会被响应。
If a cancellation request is received, it is blocked until cancelability is enabled.
//如果收到取消请求,那么就会阻塞到这个线程能响应为止。
设置能响应时,子线程收到主线程的取消请求后,立即响应退出线程
设置不能响应时,子线程收到主线程的取消请求后,会阻塞(但取消请求还在),等待可以响应的时候才会响应取消
pthread_setcanceltype() -> man 3 pthread_setcanceltype
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);
参数: type: PTHREAD_CANCEL_DEFERRED -> 延迟取消 -> 遇到一个取消点函数才会响应取消-> 线程默认属性
PTHREAD_CANCEL_ASYNCHRONOUS -> 立即响应
oldtype:保存之前的状态,如果不关心,则填NULL。
注意: 不设置取消线程的话线程是遇到取消点函数之后才会响应取消的,设置后会立即响应退出 取消点函数有哪些呢?
-> man 7 pthreads printf() fprintf() sleep() usleep() fputc() fputs()
注意:主线程给子线程 发送线程取消 ,但是子线程在执行过程中没有遇到取消点函数,所以不能响应取消请求
现象:子线程收到主线程的取消请求后,立即响应退出线程
测试代码如下:
//pthread_cancel.c
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
int p1 = 250;
void* fun1(void* arg)
{
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
int cnt = 0;
while(1)
{
printf("[%s][%d]cnt = %d\n",__FUNCTION__,__LINE__,cnt++);
sleep(1);
if(cnt == 10)
{
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
}
}
// while(1)
// {
// printf("[%s][%d]\n",__FUNCTION__,__LINE__);
// sleep(1);
// }
pthread_exit(&p1);
}
int main()
{
int ret = 0;
pthread_t pid1;
ret = pthread_create(&pid1,NULL,fun1,NULL);
if(ret != 0)
{
printf("pthread_create fail\n");
return -1;
}
printf("pthread_create ok\n");
int cnt = 1;
while(cnt++)
{
printf("[%s][%d] %d\n",__FUNCTION__,__LINE__,cnt);
sleep(1);
if(cnt == 5)
{
pthread_cancel(pid1);
break;
}
}
void* p = NULL;
ret = pthread_join(pid1,&p);
//printf("p = %d\n",*(int*)p);
return 0;
}
//pthread_setcancelstate.c
#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
int pthread_status = 20;
//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
//子线程设置不响应取消请求
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
int cnt=0;
while(1)
{
sleep(1);
printf("start_routine:%lu cnt:%d\n",pthread_self(),cnt++); //pthread_self 打印自己的线程ID号
if(cnt == 10)
{
//子线程设置 取消请求能响应
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE , NULL);
}
}
}
int main()
{
//创建一条子线程
pthread_t thread;
pthread_create(&thread,NULL,start_routine,NULL);
sleep(2);
//给子线程发送一个取消请求
pthread_cancel(thread);
//等待子线程退出
pthread_join(thread,NULL);
return 0;
}
//pthread_setcanceltype.c
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);
//pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
while(1)
{
//printf("start_routine\n");//取消点函数
//sleep(1); //取消点函数
}
}
int main()
{
//创建一条子线程
pthread_t thread;
pthread_create(&thread,NULL,start_routine,NULL);
//给子线程发送一个取消请求
pthread_cancel(thread);
//等待子线程退出
pthread_join(thread,NULL);
return 0;
}
3、线程取消例程函数
1、什么是线程取消例程函数
当线程收到取消请求时,先不要马上响应取消请求,而是执行一个例程函数先,执行完这个函数再响应取消。
2.为什么要使用取消例程函数
为了防止线程带着一些公共资源而被取消掉,如果带着资源来退出,那么其他线程无法再次使用该资源
1)压栈线程的取消例程函数-> pthread_cleanup_push()
#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *),void *arg);
参数: routine: 线程的取消例程函数 -> 必须是: void fun(void *arg) arg:传递给取消例程函数的参数
2)弹栈线程的取消例程函数 --> pthread_cleanup_pop()
#include <pthread.h>
void pthread_cleanup_pop(int execute);
参数: execute: 0 -> 删除时,直接删除。 非0 -> 删除时,会先执行一遍例程函数,再删除。
注意:
>1.子线程收到取消请求之后,(遇到取消点函数)就会执行线程取消例程函数,然后执行完就响应取消请求直接退出,不会再往下面执行了 。
>2.如果子线程没有收到取消请求,而且程序执行到pthread_cleanup_pop该函数时,此函数才会执行, 并且根据参数决定是否执行线程取消例程函数再退出子线程。
>3.这两个函数都必须是成对出现的,如果只写一个直接编译会报错
现象:子线程收到取消请求之后,立即执行取消例程函数。pop后面的函数不执行
线程取消例程函数的调用:收到取消请求或者pthread_cleanup_pop()函数的参数值不为0
测试代码如下:
#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
void routine(void *arg)
{
printf("线程取消例程\n");
}
//线程例程,创建一条线程之后,去执行这个函数
void* start_routine(void *arg)
{
int time = 5;
//压栈
pthread_cleanup_push(routine,NULL);
while(time--)
{
printf("start_routine\n");
sleep(1);
}
//弹栈
pthread_cleanup_pop(1);//0不执行 非0才执行
}
int main()
{
//创建一条子线程
pthread_t thread;
pthread_create(&thread,NULL,start_routine,NULL);
sleep(5);
//给子线程发送一个取消请求
pthread_cancel(thread);
//等待子线程退出
pthread_join(thread,NULL);
return 0;
}