言之者无罪,闻之者足以戒。 ——《诗序》
二、线程的基本控制
1、终止进程:
如果进程中的任意一个进程调用了exit、_exit、_Exit,那么整个进程就会终止
普通的单个进程有以下3种退出方式,这样不会终止进程:
(1)从启动例程中返回,返回值是线程的退出码
(2)线程可以被同一个进程中的其他进程取消
(3)线程调用pthread_exit(void *rval)函数,rval是退出码
下面我们写一个程序用一下三种返回方式:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
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 failure\n");
return 0;
}
sleep(1);
printf("main thread\n");
return 0;
}
2、线程连接:
(1)pthread_join线程连接函数
int pthread_join(pthread_t tid,void **rval)
第一个参数:tid 就是指定线程的id
第二个参数:rval就是指定线程的返回码,如果线程被取消,那么rval被置为PTHREAD_CANCELED
返回值:调用成功返回0,失败返回错误码
调用该函数的线程会一直阻塞,直到指定的线程tid调用pthread_exit,从启动例程返回或者被取消;调用该函数会使指定的线程处于分离状态,如果指定的线程已经处于分离状态,那么调用就会失败。
(2)pthread_detach分离线程函数
int pthread_detach(pthread_t thread)
参数:线程的id
返回值:成功返回0,失败返回错误码
调用该函数可以分离一个线程,线程也可以自己分离自己。
下面来看一下程序:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
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");
//自己分离自己,如果再用join链接它,就会阻塞
pthread_detach(pthread_self());
pthread_exit((void *)2);
}
int main()
{
int err1,err2;
pthread_t tid1,tid2;
void *rval1,*rval2;
err1=pthread_create(&tid1,NULL,thread_fun1,NULL);
err2=pthread_create(&tid2,NULL,thread_fun2,NULL);
if(err1 || err2)
{
printf("creatr new thread failure\n");
return -1;
}
printf("I am is main thread\n");
printf("jion1 rval is %d\n",pthread_join(tid1,&rval1));
printf("jion2 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);
printf("I am main thread\n");
return 0;
}
3、线程的取消:
(1)pthread_cancel取消线程函数
int pthread_cancel(pthread_t tid)
参数:需要取消的线程的id
返回值:成功返回0,失败返回错误码
取消tid指定的线程,所谓的取消只是发送一个请求,并不意味着等待线程终止,而且发送成功也不代表线程一定会终止。该函数需要被取消线程的配合,线程在很多时候会查看自己是否有取消请求,如果有就主动退出,这个查看是否有取消的地方称为取消点。
(2)pthread_setcancelstate设置线程对取消信号的反应
int pthread_setcancelstate(int state,int *oldstate)
第一个参数:有两个值:PTHREAD_CAMCEL_ENABLE(响应取消信号)和PTHREAD_CANCEL_DISABLE(忽略取消信号)
第二个参数:如果不为NULL则是存储原来的取消状态以便恢复
返回值:成功返回0 ,失败返回错误码
取消状态,就是线程对取消信号的处理方式,忽略或者响应。线程创建时默认的是响应取消信号。
(3)pthread_setcanceltype设置线程取消动机的执行时机
int pthread_setcanceltype(int type, int *oldtype)
第一个参数:有两个值:PTHREAD_CANCEL_DEFERRED(收到取消信号后继续运行至下一个取消点再退出)和PTHREAD_CANCEL_ASYNCHRONOUS(收到取消信号后立即退出)。注意:这两个参数是在线程响应取消信号的时候才有作用(PTHREAD_CAMCEL_ENABLE)
第二个参数:oldtype如果不设置为NULL则存储运来的取消动作类型值。
返回值:成功返回0,失败返回错误码
取消类型,是线程对取消信号的响应方式,立即取消或者延时取消。线程创建时默认是延时取消。
下面先看一下程序的框架:
程序代码:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
void *thread_fun(void *arg)
{
int stateval;
int typeval;
//设置不响应取消信号
stateval=pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
if(stateval != 0)
{
printf("set cancel state failure\n");
}
printf("I am xiaoyi\n");
//睡眠,返回执行主线程
sleep(4);
printf("about to cancel\n");
//设置为响应取消信号
stateval=pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
if(stateval != 0)
{
printf("set cancel state failure\n");
}
//取消方式为延迟取消
typeval=pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);
if(typeval != 0)
{
printf("set cancel type failure\n");
}
//因为设置的是延迟取消,所以在第一个取消点到来时就会取消,所以看不到第二句话的打印信息
printf("first cancel point\n");
printf("second cancel point\n");
return (void *)10;
}
int main()
{
pthread_t tid;
int err,cval,jval;
void *rval;
//创建新的线程
err=pthread_create(&tid,NULL,thread_fun,NULL);
if(err != 0)
{
printf("create thread failure\n");
return -1;
}
//睡眠,执行新的线程
sleep(2);
//发送取消信号
cval=pthread_cancel(tid);
if(cval != 0)
{
printf("cancel thread failure\n");
}
jval=pthread_join(tid,&rval);
printf("new thread exit code is %d\n",(int *)rval);
return 0;
}
4、向线程发送信号:
(1)pthread_kill发送信号函数
int pthread_kill(pthread_t thread,int sig)
第一个参数:线程的id
第二个参数:要发送的信号
返回值:成功返回0,失败返回错误码
作用:向指定ID的线程发送sig信号,如果线程代码内不做处理,则按照信号默认的行为影响整个进程,也就是说,如果你给一个线程发送了SIGQUIT信号,但线程却没有实现signal处理函数,则整个进程会退出
如果第二个参数不是0 ,那就一定要清楚到底要干什么,而且一定要实现线程的信号处理,否则就会影响整个进程;如果第二个参数是0 ,这就是一个保留信号,其实并没有发送信号,作用是用来判断线程是不是还活着。
下面给出一段代码熟悉一下知识:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
void *thread_fun(void *arg)
{
// sleep(1);
printf("I am xiaoyi\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 failure\n");
return -1;
}
sleep(1);
s=pthread_kill(tid,0);
// s=pthread_kill(tid,SIGQUIT);
// if(s == ESRCH)
// {
// printf("thread tid is not found\n");
// }
if(s != 0)
{
printf("thread tid is not found\n");
}
// pthread_join(tid,&rval);
printf("I am main thread\n");
return 0;
}
(2)sigaction设置一个信号的处理函数
int sigaction(int signum, const struct sigaction *act , struct sigaction *oldact)
第一个参数:信号的名字
第二个参数:传入新的处理方式
第三个参数:传出旧的处理方式
返回值:成功返回0,失败返回错误码
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
sa_handler:指定信号捕捉后的处理函数名,也可以赋值为SIG_IGN表示忽略或SIG_DFT表示执行默认操作
sa_mask:调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。用sigaddset函数添加需要被捕捉的信号。
sa_flags:通常设置为0,表示使用默认属性,为0的时候,可以屏蔽正在处理的信号(若在处理二号信号的时候又有二号信号,则此时传来的二号信号就会被屏蔽)
(3)sigemptyset清空信号集
int sigemptyset(sigset_t *set)
参数:信号集
返回值:成功返回0,失败返回-1
(4)sigfillset将所有信号加入信号集
int sigfillset(sigset_t *set)
参数:信号集
返回值:成功返回0,失败返回-1
(5)sigaddset增加一个信号到信号集
int sigaddset(sigset_t *set , int signum)
第一个参数:信号集
第二个参数:要增加的信号
返回值:成功返回0,失败返回-1
(6)sigdelset将指定信号从信号集中删除
int sigdelset(sigset_t *set , int signum)
第一个参数:信号集
第二个参数:要删除的信号
返回值:成功返回0,失败返回-1
(7)pthread_sigmask在主线程中控制信号掩码
int pthread_sigmask(int show ,const sigset_t set, sigset_t *oldset)
第一个参数: SIG_BLOCK:向当前的信号掩码中添加set,其中set表示要阻塞的信号组。
SIG_UNBLOCK:向当前的信号掩码中删除set,其中set表示要取消阻塞的信号组。
SIG_SETMASK:将当前的信号掩码替换为set,其中set表示新的信号掩码。
第二个参数:信号组
第三个参数:以前的信号组
返回值:成功返回0 ,失败返回错误码
在多线程中,新线程的当前信号掩码会继承创造它的信号掩码。
一般情况下被阻塞的信号将不能中断此线程的执行,除非此信号的产生是因为程序运行出错;如SIGSEGV,另外不能被忽略处理的信号 SIGKILL 和 SIGSTOP 也无法被阻塞。
我们写一个程序学习一下上面的这些函数:
先看一下程序的框架:
再看一下程序的代码:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
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("new thread 1\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);
}
void *thread_fun2(void *arg)
{
printf("new thread 2\n");
struct sigaction act;
//清空结构体变量
memset(&act,0,sizeof(act));
//添加一个信号到信号集
sigaddset(&act.sa_mask,SIGQUIT);
//设置一个信号的处理函数名
act.sa_handler = sig_handler2;
//设置一个信号的处理函数
sigaction(SIGQUIT,&act,NULL);
//添加要阻塞的信号
pthread_sigmask(SIG_BLOCK,&act.sa_mask,NULL);
sleep(2);
}
int main()
{
pthread_t tid1,tid2;
int err;
int s;
//创建新的线程 1
err = pthread_create(&tid1,NULL,thread_fun1,NULL);
if(err != 0)
{
printf("create new thread 1 failure\n");
return ;
}
//创建新的线程 2
err = pthread_create(&tid2,NULL,thread_fun2,NULL);
if(err != 0)
{
printf("create new thread 2 failure\n");
return ;
}
//主线程睡眠,执行新的线程
sleep(1);
//向新的线程 1 发送信号
s=pthread_kill(tid1,SIGQUIT);
if(s != 0)
{
printf("send signal to thread1 faailure\n");
}
//向新的线程 2 发送信号
s=pthread_kill(tid2,SIGQUIT);
if(s != 0)
{
printf("send signal to thread2 faailure\n");
}
//链接两个新的线程
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return 0;
}
5、清除操作
线程可以安排它退出时的清理操作,这与进程可以用atexit函数安排进程退出时需要调用的函数类似。这样的函数称为线程清理处理程序。线程可以建立多个清理处理程序,处理程序记录在栈中,所以这些处理程序执行的顺序与他们注册的顺序相反(先注册的后执行)。
(1)pthread_cleanup_push注册处理程序
void pthread_cleanup_push(void (*routine)(void *), void *arg)
第一个参数:一个处理函数
第二个的参数:处理函数的入口参数
返回值:无
(2)pthread_cleanup_pop清除处理程序
void pthread_cleanup_pop(int execute)
参数:非零即调用
返回值:无
当执行以下操作时调用清理函数,清理函数的参数由arg传入:
1)调用pthread_exit
2)响应取消请求
3)用非零参数调用pthread_cleanup_pop
下面来看一下程序:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
void *first_clean(void *arg)
{
printf("%s first 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("new thread 1 \n");
//注册处理程序
pthread_cleanup_push(first_clean,"xiaoyi1");
pthread_cleanup_push(second_clean,"xiaoyi1");
//调用清除处理程序函数(入口参数非零即调用)
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
return (void *)1;
}
void *thread_fun2(void *arg)
{
printf("new thread 2 \n");
//注册处理程序
pthread_cleanup_push(first_clean,"xiaoyi2");
pthread_cleanup_push(second_clean,"xiaoyi2");
//调用清除处理程序函数(入口参数非零即调用)
pthread_cleanup_pop(0);
pthread_cleanup_pop(1);
pthread_exit((void *)2);
}
int main()
{
pthread_t tid1,tid2;
int err;
//创建新的线程 1
err =pthread_create(&tid1, NULL, thread_fun1, NULL);
if(err != 0)
{
printf("create new thread 1failed\n");
return;
}
//创建新的线程 2
err =pthread_create(&tid2, NULL, thread_fun2, NULL);
if(err != 0)
{
printf("create new thread 2failed\n");
return;
}
//主线程休眠,执行新线程
sleep(2);
return 0;
}
到这里为止线程的基本操作就说完了。