Linux---多线程编程(二)---多线程的控制


前言

本文主要讲解了线程的终止,连接,取消,信号,清除
涉及如下函数:
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—多线程编程(三)—线程同步与锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SigmaBull

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值