10.4 Linux_并发_线程
概述
线程的共享资源:
可执行的指令、静态数据、文件描述符、当前工作目录、用户ID、用户组ID
线程的私有资源:
线程ID、程序计数器PC和相关寄存器、堆栈、错误号、优先级、执行状态和属性
线程编译:
gcc <.c文件> -l pthread -o <可执行文件>
线程的运行特点:
如果主线程main退出,则其他线程也被终止。因为main退出,进程的空间被释放,所有线程的空间也不复存在。
命令行查看线程状态:
ps -eLf | grep <程序名>
相关函数
1、创建线程
函数声明如下:
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg);
返回值:成功返回0,失败返回错误码
thread:线程对象,将创建的新线程的TID号存入该变量
attr:线程的属性,NULL代表默认的属性。
对于默认属性,线程占有的资源不会因执行结束而释放。
start_routine:线程的回调函数
arg:线程的回调函数的参数
2、线程退出
函数声明如下:
void pthread_exit(void *retval);
retval:线程退出时的返回值,该值可被pthread_join获取。
注意:当线程调用该函数后,线程结束,但线程的资源未被回收,最终资源被pthread_join回收。即:功能与exit退出进程类似
3、获取自己的线程ID(TID)
函数声明如下:
pthread_t pthread_self(void);
返回值:自己的TID号
4、线程回收
函数声明如下:
int pthread_join(pthread_t thread, void **retval);
返回值:成功返回0,失败返回错误码
thread:线程ID
retval:接收pthread_exit的返回值
注意:调用该函数是线程会进入阻塞状态,直到被等待的线程调用pthread_exit结束。被等待的线程结束后,pthread_join会回收退出线程的资源,并且解除阻塞
注意:pthread_join只能依次回收线程。比如先回收线程0,再回收线程1,如果这时线程0一直不结束,而线程1先结束,此时线程1不会被回收,而是等待线程0结束之后,线程1才回收。
5、线程分离
5.1 调用函数
函数声明如下:
int pthread_detach(pthread_t thread);
返回值:成功返回0,失败返回错误码
thread:线程ID
注意:调用该函数后指定的线程与主控线程断开关系,指定的线程结束后不会产生僵尸线程,即:自动回收资源。该函数可以由主线程调用,也可以由新创建的线程调用。
5.2 创建时设置线程属性
函数声明如下:
//初始化线程属性变量attr
int pthread_attr_init(pthread_attr_t *attr);
//设置线程属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
attr: 线程属性
detachstate:分离状态,写 PTHREAD_CREATE_DETACHED
设置完成之后,将attr传入pthread_create的第二个参数attr即可在创建时完成线程分离。
6、取消线程
取消线程的作用是让主线程能够主动的让创建的线程进行退出,也可以让自己取消自己。
函数声明如下:
//取消线程
int pthread_cancel(pthread_t thread);
//手动设置取消点
void pthread_testcancel(void);
//设置取消状态
int pthread_setcancelstate(int state, int *oldstate);
//设置取消的时刻
int pthread_setcanceltype(int type, int *oldtype);
thread:线程ID
state:线程是否可以被取消
允许被取消:PTHREAD_CANCEL_ENABLE(默认)
禁止被取消:PTHREAD_CANCEL_DISABLE
type:线程取消时刻的模式
等到取消点才取消:PTHREAD_CANCEL_DEFERRED(默认)
目标线程会立刻取消:PTHREAD_CANCEL_ASYNCHRONOUS
oldstate:传入NULL
注意:线程的取消要有取消点,取消点就是阻塞的系统调用。当没有取消点时,该函数不会进入阻塞,并且对指定的线程也没有任何影响。
注意:调用pthread_cancel,指定的线程被取消,这意味着该线程在调用pthread_exit之前进行了退出。除此之外,pthread_cancel会自动回收线程的资源。
pthread_testcancel的使用:
该函数手动设置取消点,并不是说执行到该函数才设置,而是线程中存在这个函数,不论能否执行到,都可以进行取消。???????
pthread_setcancelstate的使用:
pthread_setcancelstate用于保护线程中的部分代码段完整执行,在这期间线程不可被取消。
使用方法如下:
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
//线程中不可被取消的代码段
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
示例见示例代码:设置线程是否可以被取消
注意:在创建完线程之后,如果直接取消该线程,那么该线程中尽管在刚开始时就禁止取消,也有可能被取消。因为创建线程需要时间,当主线程取消线程时,新的线程还没有执行禁止取消的函数。
7、线程清理
线程清理的作用是释放线程取消之前申请的内存空间,这个释放代码需要自己编写。
函数声明如下:
void pthread_cleanup_push(void (*routine)(void *),void *arg);
void pthread_cleanup_pop(int execute);
routine:清理函数
arg:清理函数的参数
execute:写0代表不执行routine,非0代表执行routine
注意:这两个函数必须成对使用,并且pthread_cleanup_push后不加分号
注意:这两个函数可以多次使用,效果类似栈,先push的后pop
注意:这两个并不是函数,而是宏定义
routine被执行的情况:
- 当主线程调用pthread_cancel取消了线程
- 该线程调用了pthread_exit退出了线程
- 执行到pthread_cleanup_pop,并且传参为非0
注意:当调用return退出线程时,routine不执行
示例代码
1、创建线程
具体代码实现如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//线程0回调函数
void* pthread_0(void* arg){
while(1){
printf("this is pthread 0\n");
sleep(1);
}
//退出线程
pthread_exit(NULL);
}
//线程1回调函数
void* pthread_1(void* arg){
while(1){
printf("this is pthread 1\n");
sleep(1);
}
//退出线程
pthread_exit(NULL);
}
int main(){
pthread_t tid[2];//多个线程时,tid用数组存储
//创建线程
if(pthread_create(&tid[0],NULL,(void*)pthread_0,NULL) != 0){
perror("pthread_create");
return -1;
}
if(pthread_create(&tid[1],NULL,(void*)pthread_1,NULL) != 0){
perror("pthread_create");
return -1;
}
while(1){
printf("this is main\n");
sleep(1);
}
return 0;
}
代码运行结果如下:
2、获取线程TID
具体代码实现如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//线程回调函数
void* pthread_0(void* arg){
//线程中查看自己的TID
printf("pthread:tid0 = %ld\n",pthread_self());
//退出线程
pthread_exit(NULL);
}
int main(){
pthread_t tid;
//创建线程
if(pthread_create(&tid,NULL,(void*)pthread_0,NULL) != 0){
perror("pthread_create");
return -1;
}
//main中查看所创建线程的TID
printf("main:tid0 = %ld\n",tid);
while(1);
return 0;
}
代码运行结果如下:
3、参数传递
3.1 传递一个参数
传递一个参数,只需要将这个参数的地址进行传递即可。
具体代码实现如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//线程回调函数
void* pthread_0(void* arg){
//获取传入的参数
printf("arg = %d\n",*((int*)arg));
//退出线程
pthread_exit(NULL);
}
int main(){
pthread_t tid;
int arg0 = 100;
//创建线程
if(pthread_create(&tid,NULL,(void*)pthread_0,&arg0) != 0){
perror("pthread_create");
return -1;
}
while(1);
return 0;
}
代码运行结果如下:
3.2 传递多个参数
传递多个参数,就是将多个参数捆为一个结构体,传入的是结构体的指针。
具体代码实现如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//线程回调函数
struct test{
int num;
char a;
};
void* pthread_0(void* arg){
//获取传入的参数
printf("arg.num = %d\n",((struct test*)arg)->num);
printf("arg.a = %c\n",((struct test*)arg)->a);
//退出线程
pthread_exit(NULL);
}
int main(){
pthread_t tid;
struct test arg0 = {300,'a'};
//创建线程
if(pthread_create(&tid,NULL,(void*)pthread_0,&arg0) != 0){
perror("pthread_create");
return -1;
}
while(1);
return 0;
}
代码运行结果如下:
3.3 传递的参数经常变化
在3.1、3.2中,传递的参数都是固定的,因此传递指针是可以的。但如果参数会在main中发送改变,那么线程在接收到指针后,访问内存的数据不一定是传入时的数据,即:访问时main已经把数据进行了修改。
针对这个问题,可以将数据直接强制转为void*,进行传递。
具体代码实现如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//线程0回调函数
void* pthread_0(void* arg){
printf("num:%d,tid = %ld\n",arg,pthread_self());
//退出线程
pthread_exit(NULL);
}
int main(){
pthread_t tid[10];//多个线程时,tid用数组存储
int i;
for(i=0;i<10;i++){
//创建线程
if(pthread_create(&tid[0],NULL,(void*)pthread_0,(void*)i) != 0){//将i强转为void*
perror("pthread_create");
return -1;
}
}
while(1){
printf("this is main\n");
sleep(1);
}
return 0;
}
代码运行结果如下:
4、等待线程退出
具体代码实现如下:
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
int i;
for(i=0;i<3;i++){
printf("this is pthread0\n");
sleep(1);
}
//线程退出
pthread_exit("pthread0 exit");
}
int main(){
pthread_t tid;
void* ret0 = NULL;
//创建线程
if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
perror("pthread_create");
return -1;
}
//等待线程退出,main进入阻塞
pthread_join(tid,&ret0);
printf("ret0:%s\n",(char*)ret0);//打印线程0退出状态
while(1);
return 0;
}
代码运行结果如下:
5、线程分离
5.1 调用pthread_detach
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
//pthread_detach(pthread_self());//也可以在这里进行线程分离
printf("this is pthread0\n");
//线程退出
pthread_exit("pthread0 exit");
}
int main(){
pthread_t tid;
pthread_t tid1;
void* ret0 = NULL;
//创建线程
if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
perror("pthread_create");
pthread_detach(tid);//线程分离
return -1;
}
while(1);
return 0;
}
5.2 设置线程属性为分离
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
printf("this is pthread0\n");
//线程退出
pthread_exit("pthread0 exit");
}
int main(){
pthread_t tid;
pthread_attr_t attr;
//初始化线程属性
pthread_attr_init(&attr);
//设置线程属性为分离
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
//创建线程,创建时传入attr
if(pthread_create(&tid,&attr,pthread0,NULL) != 0){
perror("pthread_create");
return -1;
}
while(1);
return 0;
}
6、取消线程
6.1 基本使用
具体代码实现如下:
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
while(1){
sleep(1);//进入阻塞,这里就是一个取消点
//pthread_testcancel();//也可以将阻塞换为该函数,手动设置一个取消点
}
//线程退出
pthread_exit("pthread0 exit");
}
int main(){
pthread_t tid;
void* ret0 = NULL;
//创建线程
if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
perror("pthread_create");
return -1;
}
//取消线程
pthread_cancel(tid);//取消之后pthread_exit没有运行
pthread_join(tid,&ret0);//这里的ret0不会收到pthread_exit的返回值
printf("get\n");
while(1);
return 0;
}
代码运行结果如下:
6.2 设置线程是否可以被取消
具体代码实现如下:
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void* pthread0(void* arg){
int i;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);//线程禁止被取消
for(i=0;i<3;i++){
printf("now cannot cancel\n");
sleep(1);
}
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);//线程可以被取消
while(1){
printf("now can cancel\n");
sleep(1);
}
pthread_exit("pthread0 exit");
}
int main(){
pthread_t tid;
void* ret0 = NULL;
//创建线程
if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
perror("pthread_create");
return -1;
}
sleep(1);//创建线程后不能立刻取消,需要一些时间让线程执行程序
pthread_cancel(tid);//取消线程
printf("get\n");
pthread_join(tid,&ret0);
printf("get2\n");
while(1);
return 0;
}
代码运行结果如下:
7、线程清理
7.1 routine执行情况1
当主线程调用pthread_cancel取消了线程
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void clean(void* arg){
//该函数应该执行一些清理任务,但这里为演示所以只进行一下打印
printf("arg = %s\n",(char*)arg);
}
void* pthread0(void* arg){
pthread_cleanup_push(clean,"2") //这里不能加分号
while(1){
sleep(1);//这里阻塞当作一个取消点
}
printf("after while\n");
pthread_cleanup_pop(1);
}
int main(){
pthread_t tid;
void* ret0 = NULL;
//创建线程
if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
perror("pthread_create");
return -1;
}
pthread_cancel(tid);//取消线程
pthread_join(tid,&ret0);
printf("get\n");
while(1);
return 0;
}
7.2 routine执行情况2
该线程调用了pthread_exit退出了线程
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void clean(void* arg){
//该函数应该执行一些清理任务,但这里为演示所以只进行一下打印
printf("arg = %s\n",(char*)arg);
}
void* pthread0(void* arg){
pthread_cleanup_push(clean,"1") //这里不能加分号
pthread_exit(NULL);//线程退出
printf("after exit\n");
pthread_cleanup_pop(1);
}
int main(){
pthread_t tid;
void* ret0 = NULL;
//创建线程
if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
perror("pthread_create");
return -1;
}
pthread_join(tid,&ret0);
printf("get\n");
while(1);
return 0;
}
7.3 routine执行情况3
执行到pthread_cleanup_pop,并且传参为非0
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void clean(void* arg){
//该函数应该执行一些清理任务,但这里为演示所以只进行一下打印
printf("arg = %s\n",(char*)arg);
}
void* pthread0(void* arg){
pthread_cleanup_push(clean,"1") //这里不能加分号
pthread_cleanup_pop(1);
}
int main(){
pthread_t tid;
void* ret0 = NULL;
//创建线程
if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
perror("pthread_create");
return -1;
}
pthread_join(tid,&ret0);
printf("get\n");
while(1);
return 0;
}
7.4 多个清理函数
多次push后,先push的后pop。
具体代码实现如下:
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void clean(void* arg){
//该函数应该执行一些清理任务,但这里为演示所以只进行一下打印
printf("arg = %s\n",(char*)arg);
}
void* pthread0(void* arg){
pthread_cleanup_push(clean,"1") //这里不能加分号
pthread_cleanup_push(clean,"2") //这里不能加分号
pthread_cleanup_push(clean,"3") //这里不能加分号
pthread_cleanup_pop(1);
printf("after pop\n");
while(1){
printf("now in while\n");
sleep(1);//阻塞作为取消点
}
printf("after while\n");
pthread_exit(NULL);//线程退出
printf("after exit\n");
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
}
int main(){
pthread_t tid;
void* ret0 = NULL;
//创建线程
if(pthread_create(&tid,NULL,pthread0,NULL) != 0){
perror("pthread_create");
return -1;
}
pthread_cancel(tid);
pthread_join(tid,&ret0);
printf("get\n");
while(1);
return 0;
}
代码运行结果如下: