文章目录
前言
本文主要讲解了线程的终止,连接,取消,信号,清除
涉及如下函数:
pthread_exit pthread_join pthread_detach pthread_cancel
pthread_setcancelstate pthread_setcanceltype pthread_kill
sigaction sigemptyset sigfillset sigaddset sigdelset
pthread_sigmask pthread_cleanup_push pthread_cleanup_pop
其中每个函数都有相关的例子代码帮助学习与理解
一、线程的终止
1.线程终止进程终止
任意一个线程调用了exit退出函数会直接终止进程。
2.线程终止进程不终止
线程退出但是不终止进程的方式
1.从启动函数中返回,返回值是线程的退出码
2.线程可以被同一进程中的其它线程取消
3.线程调用pthread_exit(void *rval),rval保存线程的退出码,其它线程可以通过退出码链接这个线程。
3.Eg:线程退出方式演示
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
//编译程序需要用链接库
//gcc -lpthread xxx.c -o xxx
//xxx.c是编写的C语言源文件名
//xxx是你想要生成的可执行文件名
//实现效果:
//用第三种方式退出,不会打印main thread这句话
//其它方式都会打印main thread这句话
void *thread¬_fun(void* arg)
{
if(strcmp(“1”,(char*)arg)==0)
{
printf(“new thread return\n”);
return (void*)1;
}
if(strcmp(“2”,(char*)arg)==0)
{
printf(“new thread pthread_exit\n”);
pthread_exit((void*)2);
}
if(strcmp(“3”,(char*)arg)==0)
{
printf(“new thread exit\n”);
exit(3);
}
}
int main(int argc,char* argv[])
{
int err;
pthread_t tid;
err=pthread_create(&tid,NULL,thread_fun,(void*)argv[1]);
if(err!=0)
{
printf(“create new thread failed!\n”);
return 0;
}
sleep(1);
printf(“main thread:\n”);
return 0;
}
二、线程的连接
1.线程连接函数
int pthread_join(pthread_t tid,void **rval);
调用这个函数的线程会一直拥塞,直到指定的线程tid调用pthread_exit,或者启动函数中返回,或则是直接被取消
1.参数 tid:是指定线程的ID
2.参数rval:是指定线程的返回码(退出码),如果线程被取消,那么rval被设置为PTHREAD_CANCELED(被定义为“(void*)-1“),因为不是正常的退出,不会有退出码。
返回值:成功返回0,失败返回错误码
3.理解:本来各个线程之间在时间片轮上运行是相互独立的,按照一定的方式轮流执行。但是有了pthread_join函数,就可以确认两个线程之间的执行的先后关系,这样就是一种建立连接的方式,故可以称为线程的连接。
4.注意:调用pthread_join会使指定线程处于分离状态,如果线程已经处于分离状态,那么调用就会失败。
2.线程分离函数
int pthread_detach(phread_t tid);
该函数可以分离指定线程,成功返回0,失败返回.
3.Eg:线程的链接举例:
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
//编译程序需要用链接库
//gcc -lpthread xxx.c -o xxx
//xxx.c是编写的C语言源文件名
//xxx是你想要生成的可执行文件名
//实现效果
//第一个pthread_join函数返回值是0
//第二个pthread_join函数返回值是22,是错误码,意思是线程不可链接
//第一个退出码是1,就是thread_fun1所return的1
//第二个退出码是乱码。因为没有链接成功,就不会保存退出码
void* thread_fun1(void *arg)
{
printf(“I am thread 1\n”);
return (void *)1;
}
void* thread_fun2(void *arg)
{
printf(“I am thread 2\n”);
pthread_detach(pthread_self());
pthread_exit((void *)2);
}
int main()
{
int err1;
int err2;
pthread_t tid1;
pthread_t tid2;
void* rval1;
void* rval2;
err1=pthread_create(&tid1,NULL,pthread_fun1,NULL);
err2=pthread_create(&tid2,NULL,pthread_fun2,NULL);
if(err1||err2)
{
printf(“create new thread failed\n”);
return 0;
}
printf(“I am main thread\n”);
printf(“join1 rval is %d\n”,pthread_join(tid1,&rval1));
printf(“join2 rval is %d\n”,pthread_join(tid2,&rval2));
printf(“thread 1 exit code is %d\n”,(int *)rval1);
printf(“thread 2 exit code is %d\n”,(int *)rval2);
return 0;
}
三、线程的取消
1.线程取消函数
int pthread_cancel(pthread_t tid);
该函数用来取消tid指定的线程,成功返回0。
注意:这里的取消只是发送一个取消请求,并不会等待指定线程的终止。况且取消仅仅是一个请求,指定线程不一定会终止。
2.取消状态
概念:取消状态指的是该线程对于取消请求信号的处理方式,忽略或者响应。线程创建时的默认状态是响应取消信号的。
设置函数:
int pthread_setcancelstate(int state,int *oldstate);
1.state有两种值
PTHREAD_CANCEL_ENABLE(缺省),接收到取消信号的线程取消
PTHREAD_CANCEL_DISABLE,忽略取消信号,继续运行
2.oldstate是一个指针变量,相当于一个地址,可以是NULL;也可以是一个有效的地址,调用函数后,函数会将线程之前的取消状态放入该地址的存储空间。
3.取消类型
概念:取消类型是该线程对于响应取消信号的方式,分为立即取消或者是延时取消。线程创建时默认是延时取消。
设置类型函数
int pthread_setcanceltype(int type,int *oldtype);
1.type有两种值:
PTHREAD_CANCEL_DEFFERED(延时取消,即收到取消信号后,运行至下一个取消点后取消)
(这里的取消点是已经规定好的一些语句,后面有详细介绍)
PTHREAD_CANCEL_ASYCHRONOUS(立即取消)
2.oldtype是一个指针变量,相当于一个地址,可以是NULL;也可以是一个有效的地址,调用函数后,函数会将线程之前的取消类型放入该地址的存储空间。
4.取消点
取消一个线程,它通常需要被取消线程的配合。线程在很多时候会查看自己是否有取消请求。如果有就主动退出,这些查看是否有取消的地方称为取消点。
可以使用命令查看取消点:
man pthreads
之后可以找标签:Cancellation Points,该标签下就是取消点
比如说:
Pthread_join() pthread_testcancel() pthread_cond_wait() pthread_cond_timedwait
Sem_wait() sigwait() ,write read printf()
5.Eg:正确取消一个线程,延时取消
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
//编译程序需要用链接库
//gcc -lpthread xxx.c -o xxx
//xxx.c是编写的C语言源文件名
//xxx是你想要生成的可执行文件名
//执行效果:
//I am new thread
//about to cancel
//first cancel point
//new thread exit code is -1
//可以看到second cancel point并没有打印出来
//说明了延时取消至取消点结束,在本例子中是first cancel point打印之后取消的
//取消后子线程的返回码不是20,是-1
//这也印证了线程连接所讲的知识点:被取消的线程会返回PTHREAD_CANCELED
void *thread_fun(void *arg)
{
int stateval;
stateval=pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
if(stateval!=0)
{
printf(“set cancel state failed\n”);
}
printf(“I am new thread\n”);
sleep(4);
printf(“about to cancel\n”);
stateval=pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
if(stateval!=0)
{
printf(“set cancel state failed\n”);
}
pintf(“frist cancel point\n”);
printf(“second cancel point\n”);
return (void *)20;
}
int main()
{
pthread_t tid;
int err;
int cval;
int jval;
void *rval;
err=pthread_create(&tid,NULL,thread_fun,NULL);
if(err!=0)
{
printf(“create new thread failed\n”);
return 0;
}
sleep(2);
cval=pthread_cancel(tid);
if(cval!=0)
{
printf(“cancel thread failed\n”);
}
jval=pthread_join(tid,&rval);
printf(“new thread exit code is %d\n”,(int*)rval);
return 0;
}
6.Eg:正确取消一个线程,立即取消
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
//编译程序需要用链接库
//gcc -lpthread xxx.c -o xxx
//xxx.c是编写的C语言源文件名
//xxx是你想要生成的可执行文件名
//执行效果:
//I am new thread
//about to cancel
//new thread exit code is -1
//可以看到second cancel point和frist cancel point 并没有打印出来
//说明了立即取消
//取消后子线程的返回码不是20,是-1
//这也印证了线程连接所讲的知识点:被取消的线程会返回PTHREAD_CANCELED
void *thread_fun(void *arg)
{
int stateval;
int typeval;
stateval=pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
if(stateval!=0)
{
printf(“set cancel state failed\n”);
}
printf(“I am new thread\n”);
sleep(4);
printf(“about to cancel\n”);
stateval=pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
if(stateval!=0)
{
printf(“set cancel state failed\n”);
}
typeval=pthread_setcanceltype(PTHREAD_CANCEL_ASYCHRONOUS,NULL);
if(typeval!=0)
{
printf(“set cancel type failed\n”);
}
pintf(“frist cancel point\n”);
printf(“second cancel point\n”);
return (void *)20;
}
int main()
{
pthread_t tid;
int err;
int cval;
int jval;
void *rval;
err=pthread_create(&tid,NULL,thread_fun,NULL);
if(err!=0)
{
printf(“create new thread failed\n”);
return 0;
}
sleep(2);
cval=pthread_cancel(tid);
if(cval!=0)
{
printf(“cancel thread failed\n”);
}
jval=pthread_join(tid,&rval);
printf(“new thread exit code is %d\n”,(int*)rval);
return 0;
}
四、线程的信号
1.发送信号函数
int pthread_kill(pthread_t tid,int sig);
头文件:#include<signal.h>
功能:向由tid指定的ID的线程发送sig信号,如果线程代码内不做处理,则按照信号默认的行为影响整个进程。比如,给一个线程发送SIGQUIT,但线程却没有相应的信号处理函数,则采取默认行为,即退出整个进程
第一个参数是指定线程的ID
第二个参数sig:
可以为0,在为0的情况下,是一个保留信号,相当于没有发送信号没作用是用来判断线程是不是还活着。
如果是非0,那么就要实现相应的信号处理函数,否则就会影响整个进程。
返回值:
如果成功,返回0
如果失败,返回错误码:
ESRCH:没有找到指定ID的线程,即指定ID的线程不存在
EINVAL:参数sig无效
2.Eg:验证返回值的错误码
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
//编译程序需要用链接库
//gcc -lpthread xxx.c -o xxx
//xxx.c是编写的C语言源文件名
//xxx是你想要生成的可执行文件名
//执行效果
//I am new thread
//thread tid is not found
//分析:
//在发送信号之前,子线程已经退出,故返回错误码ESRCH
void *thread_fun(void* arg)
{
printf(“I am new thread\n”);
return (void*)0;
}
int main()
{
pthread_t tid;
int err;
int s;
err=pthread_create(&tid,NULL,thread_fun,NULL);
if(err!=0)
{
printf(“create new thread failed\n”);
return 0;
}
sleep(1);
s=pthread_kill(tid,0);
if(s==ESRCH)
{
printf(“thread tid is not found\n”);
}
return 0;
}
3.Eg:验证SIGQUIT信号的默认操作
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
//编译程序需要用链接库
//gcc -lpthread xxx.c -o xxx
//xxx.c是编写的C语言源文件名
//xxx是你想要生成的可执行文件名
//执行效果:
//什么也不会打印出来
//分析:
//在发送信号SIGQUIT后,子线程没有相应的信号处理函数,默认操作是直接退出,又加上//子线程开头的sleep,故发送信号后会直接退出。而此过程发送信号会成功,因此也不会 //有错误信息被打印。
void *thread_fun(void* arg)
{
sleep(1);
printf(“I am new thread\n”);
return (void*)0;
}
int main()
{
pthread_t tid;
int err;
int s;
void *rval;
err=pthread_create(&tid,NULL,thread_fun,NULL);
if(err!=0)
{
printf(“create new thread failed\n”);
return 0;
}
s=pthread_kill(tid,SIGQUIT);
if(s==ESRCH)
{
printf(“thread tid is not found\n”);
}
pthread_join(tid,&rval);
printf(“I am main thread\n”);
return 0;
}
4.信号处理函数
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
头文件:#include<signal.h>
功能:给信号signum设置一个处理函数,处理函数在sigaction中指定
参数:是一个结构体,成员变量有:
act.sa_mask 信号屏蔽字
act.sa_handler 信号集处理程序名,处理函数的名字在这里指定
等等……
参数oldact:
像上面讲的一样,是一个回填地址,可以为NULL,如果不是NULL,那么会将原始的线程的数据回填入该地址指向的存储空间。
注意:
每个线程的信号处理函数以最后一次调用sigaction为主。接下来的例子会体现出来
5.信号集控制函数组
int sigemptyset(sigset_t *set);
//清空信号集
int sigfillset(sigset_t *set);
//将所有信号加入信号集
int sigaddset(sigset_t *set,int signum);
//增加一个信号到信号集
int sigdelset(sigset_t *set,int signum);
//删除一个信号到信号集
6.信号屏蔽函数
int pthread_sigmask(int how,const sigset_t *set,sigset_t *oldset);
参数how:
SIG_BLOCK:向当前的信号掩码中添加set,其中set表示要阻塞的信号组
SIG_UNBLOCK:向当前的信号掩码中删除set,其中set表示要取消阻塞的信号组
SIG_SETMASK:将当前的信号掩码替换为set,其中set表示新的信号掩码
参数oldset:
像上面讲的一样,是一个回填地址,可以为NULL,如果不是NULL,那么会将原始的线程的数据回填入该地址指向的存储空间。
注意:
1.在多线程编程中,新线程的当前信号掩码会继承创造它的那个线程的信号掩码。
2.区别于sigaction函数,pthread_sigmask是每个线程可以设置各自的屏蔽。
3.一般情况下,被阻塞的信号将不能中断此线程的执行,除非此信号的产生是因为程序运行出错如:SIGSEVGV;另外不能忽略处理的信号SIGKILL和SIGSTOP也无法被阻塞。
7.Eg:信号处理实例
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
//编译程序需要用链接库
//gcc -lpthread xxx.c -o xxx
//xxx.c是编写的C语言源文件名
//xxx是你想要生成的可执行文件名
//执行效果:
//new thread2
//new thread1
//thread1 get signal
//或者
//new thread1
//new thread2
//thread2 get signal
//分析:
//前面讲过,线程的信号处理函数以sigaction最后一次执行为主。由于我们两个子线程在//sigaction 执行后都会睡眠,来等待主线程的信号发送过来。因此睡眠结束,当信号发送过//来的时候,此时两个线程的信号处理函数都是最后一次sigaction指定的信号处理函数。又//由于有一个子线程将信号SIGQUIT屏蔽掉了,因此仅仅会打印一次信号处理函数的内容。
void sig_handler1(int arg)
{
printf(“thread1 get signal\n”);
return ;
}
void sig_handler2(int arg)
{
printf(“thread2 get signal\n”);
return ;
}
void *thread_fun1(void* arg)
{
printf(“I am new thread1\n”);
struct sigaction act;
memset(&act,0,sizeof(act));
sigaddset(&act.sa_mask,SIGQUIT);
act.sa_handler=sig_handler1;
sigaction(SIGQUIT,&act,NULL);
pthread_sigmask(SIG_BLOCK,&act.sa_mask,NULL);
sleep(2);
return (void*)0;
}
void *thread_fun2(void* arg)
{
printf(“I am new thread2\n”);
struct sigaction act;
memset(&act,0,sizeof(act));
sigaddset(&act.sa_mask,SIGQUIT);
act.sa_handler=sig_handler2;
sigaction(SIGQUIT,&act,NULL);
sleep(2);
return (void*)0;
}
int main()
{
pthread_t tid1,tid2;
int err;
int s;
void *rval;
err=pthread_create(&tid1,NULL,thread_fun1,NULL);
if(err!=0)
{
printf(“create new thread1 failed\n”);
return 0;
}
err=pthread_create(&tid2,NULL,thread_fun2,NULL);
if(err!=0)
{
printf(“create new thread2 failed\n”);
return 0;
}
sleep(1);
s=pthread_kill(tid1,SIGQUIT);
if(s!=0)
{
printf(“send signal to thread1 failed\n”);
}
s=pthread_kill(tid2,SIGQUIT);
if(s!=0)
{
printf(“send signal to thread2 failed\n”);
}
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return 0;
}
五、线程的清除
1.概念
线程可以安排它退出时的清除操作。这与进程的可以用atexit函数安排进程退出时需要调用的函数类似。这样的函数称为线程清除处理程序。线程可以建立多个清除处理程序,处理程序记录在栈中,所以这些处理程序的顺序与它们的入栈顺序相反。
2.注册/销毁清理函数
void pthread_cleanup_push(void(*routine)(void*),void *arg)//注册处理程序,参数用arg传递
void pthread_cleanup_pop(int execute)//清除处理程序
头文件:#include<pthread.h>
注意:当执行一下操作时调用清理函数,清理函数的参数由arg传入
1.调用pthread_exit
2.响应取消请求
3.用非零参数调用pthread_cleanup_pop
3.Eg:清除操作实例
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
//编译程序需要用链接库
//gcc -lpthread xxx.c -o xxx
//xxx.c是编写的C语言源文件名
//xxx是你想要生成的可执行文件名
//执行效果:
//I am new thread2
//I am new thread1
//thread1 second clean
//分析
//只有thread1 second clean被执行出来了
//对应于只有一个pop(1)
//那么对应于第二个子线程为什么有pthread_exit还没有执行呢?
//是因为在调用pthread_exit前已经用pop(0)取消了
void *frist_clean(void *arg)
{
printf(“%s firsrt clean\n”,arg);
return (void *)0;
}
void *second_clean(void *arg)
{
printf(“%s second clean\n”,arg);
return (void *)0;
}
void *thread_fun1(void* arg)
{
printf(“I am new thread1\n”);
pthread_cleanup_push(first_clean,”thread1”);
pthread_cleanup_push(second_clean,”thread1”);
pthread_cleanup_pop(1);
pthread_cleanup_pop(0);
return (void*)1;
}
void *thread_fun2(void* arg)
{
printf(“I am new thread2\n”);
pthread_cleanup_push(first_clean,”thread2”);
pthread_cleanup_push(second_clean,”thread2”);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_exit((void *)2);
}
int main()
{
pthread_t tid1,tid2;
int err;
err=pthread_create(&tid1,NULL,thread_fun1,NULL);
if(err!=0)
{
printf(“create new thread1 failed\n”);
return 0;
}
err=pthread_create(&tid2,NULL,thread_fun2,NULL);
if(err!=0)
{
printf(“create new thread2 failed\n”);
return 0;
}
sleep(2);
return 0;
}
总结
本篇是Linux多线程开发基础篇的第二部分,之后会有更多的内容。
讲解不清晰,代码有错误的地方请大家包容指正。
上一篇: Linux—多线程编程(一)—线程的创建和它的生命周期
下一篇: Linux—多线程编程(三)—线程同步与锁