当前位置: 首页 > article >正文

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;
}

代码运行结果如下:


http://www.kler.cn/a/330027.html

相关文章:

  • JavaScript 中,.call()的使用详解
  • Excel如何把两列数据合并成一列,4种方法
  • 青少年编程与数学 02-003 Go语言网络编程 21课题、Go语言WebSocket编程
  • NIST 发布后量子密码学转型战略草案
  • IO流(九):打印流-字节打印流PrintStream、字符打印流PrintWriter
  • 内容占位符:Kinetic Loader HTML+CSS 使用CSS制作三角形原理
  • 深入探讨 Docker:远程登录与镜像管理
  • C++容器之list基本使用
  • 上海我店:创新模式引领本地生活新风尚
  • c#使用winscp库实现FTP/SFTP/SCP的获取列表、上传和下载功能
  • 大数据比懂知识点:Parquet、ORC还是Avro作为数据存储格式,哪种在性能和压缩率上更优
  • 【C++二分查找 前缀和】1712. 将数组分成三个子数组的方案数|2078
  • 深入解析开源大模型的GPU资源需求与优化策略
  • 程序员如何通过专业与软技能提升核心竞争力
  • 特权访问管理阻力最小的途径
  • 付费计量系统通用功能(9)
  • 企望制造ERP系统存在RCE漏洞
  • UniVue大版本更新:UniVue2.0.0-preview
  • 10月2日笔记(内网资源探测篇)
  • 前端的全栈混合之路Meteor篇:运行在浏览器端的数据库-MiniMongo介绍及其前后端数据实时同步示例
  • 矩阵系统源码搭建,OEM贴牌,源头技术开发
  • 前端的全栈混合之路Meteor篇:3.0新版本介绍
  • vscode使用yarn 启动vue项目记录
  • 一个好用的服务治理组件Sentinel
  • 利士策分享,行走•悟世•惜福: 旅行真谛
  • nginx常用的性能优化