35.Linux应用层开发---线程

2023-05-16

 

一.线程的概念和使用

1.线程的概念

为了进一步减少处理器的空转时间,支持多处理器以及减少上下文切换开销,进程在演化中出现了另外一个概念线程。

它是进程内独立的一条运行路线,是内核调度的最小单元,也被称为轻量级的进程。

以前对线程和进程真的是傻傻分不清,面试中也经常会出现线程和进程的区别之类的问题。其实也比较好区分。

从上图我们可以看出线程和进程的关系,图片上看感觉线程是进程的子集?其实线程属于轻量级的进程,假如我们的qq是一个进程,那么里面的聊天功能,打字功能等子模块也需要一个个进程来完成吗??显然是可以完成的,但是感觉有点大材小用,切换时系统开销肯定是很大的,用户体验也会非常的差,那么就出现了线程。

同一进程中的线程共享相同的地址空间。当然线程有共有的部分,也有自己私有的部分(TCB 堆栈 寄存器等)。

2.线程的特点:

1> 大大提高了任务的切换效率

2> 避免了额外的TLB & cache的刷新。那什么是cache,它其实就是一个高速内存,它很快到但是它也很小,cpu存取要先从cache中找,当没有的时候cpu会通过TLB(内存映射),将内存中的数据放入cache中,如果进程较大,就需要cache频繁切换,效率下降。

3.进程资源占用

一个进程中可以有多个线程,线程之间有一些资源是共享的 ,就像进程一样,有私有的也有自己独有的部分。

一般共享以下资源:

可执行的指令,静态数据,进程中打开的文件描述符,当前工作目录,用户ID,用户组ID

私有资源部分:

线程ID (TID),PC(程序计数器)和相关寄存器,堆栈,错误号 (errno),优先级,执行状态和属性

 

二.线程的基本操作

需要使用线程库,pthread线程库中提供了如下基本操作

1.创建线程

 #include  <pthread.h>

 int  pthread_create(pthread_t *thread, const

       pthread_attr_t *attr, void *(*routine)(void *), void *arg);

例:

void *funct(void* arg){
	printf("this is thread ");
	sleep(5);
}

int main(){
	int re,i;
	pthread_t tid;
	for(i=0;i<100;i++){
		re = pthread_create(&tid, NULL,funct, NULL);
		pthread_detch(tid); //后面会讲,用于回收
		if(re!=0){
			printf("pthread_create:%s\n",strerror(re));
			exit(0);
		}
	}
	
    sleep(1); //需要加延时,不然进程就结束了,线程得不到运行也跟着退出了
}

注:进程结束,线程也会跟着结束。

ps -eLf|grep cthread //来查看进程中的线程

多线程程序中,任何一个线程使用exit(0)都会导致整个进程结束。

2.回收线程

线程不回收就会出现僵尸线程,同样也要回收。

先查看线程ID

然后top -p 2933(进程号) 查看内存占用。

 #include  <pthread.h>

 int  pthread_join(pthread_t thread, void **retval);

 if (pthread_create(&tid, NULL, thread_func, NULL) != 0){
        printf(“fail to pthread_create”);  exit(-1);
 }
	
 re = pthread_join(tid,NULL);
		if(re!=0){
		    printf("pthread_join:%s\n",strerror(re));
		    return;
            }
 }	

这样就完成指定线程的释放,此时的第二个参数下面会用到。

3.结束线程

void  pthread_exit(void *retval); 返回值,可被pthread_join回收,通常配合join使用

 void *funct(void* arg){
	int ret;
	printf("this is funct thread\n");
	sleep(1);
	ret = 5;
	pthread_exit((void*)ret); //线程退出,将参数返回
}
int main(){
	
	int re,i;
	pthread_t tid;

	re = pthread_create(&tid, NULL,funct, (void *)i);	
	
	
	void *retval;
	pthread_join(tid,&retval);// 获取exit的返回参数
	printf("retval=%d\n",(int)retval);
	
}

4.线程取消

int pthread_cancel(pthread_t thread);

本质上给线程发一个信号,这个使用前要确保线程是能被取消属性,并且是不是在cancel点(阻塞函数 sleep等)上此时才能取消成功.

还有一些配合使用的函数:

1》 int pthread_setcancelstate(int state, int *oldstate); 设置线程属性是可以被取消还是不可以,如果设置了不可取消属性,调用pthread_cancel,也不能被取消。

例:pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);//设置为不可取消

2》int pthread_setcanceltype(int type, int *oldtype);设置时延时取消还是立即取消

如果为延时取消,就运行到cancel点时取消,阻塞函数,sleep等都是cancel点.

如果没有cancel点呢??可以人为的加一个void pthread_testcancel(void);通过这个函数。立即取消不需要cancel点。

5.修改detach属性

pthread_detach,改变为detach属性后,不需要再用join进行回收,感觉这个更方便。

re = pthread_create(&tid, NULL,funct, (void *)i);

pthread_detach(tid);

 

三.线程间同步互斥机制

我们在同一个进程中创建了多个线程,他们共享地址空间,通过全局变量就可以进行数据的交互,当时多个线程访问共享资源的时候就需要同步和互斥,和实时系统中是一样的。以前我们在实时系统中都知道用信号量进行操作,互斥信号量进行互斥,这里都是一样的。

在linux中它有更洋气的名字叫原子操作。

操作方式:

1.初始化

2.P操作(申请资源)

3.V操作(释放资源)

原理上没啥区别。

posix中定义了两类信号量:

无名信号量(基于内存的信号量)只用于线程间同步和互斥

有名信号量,保存在文件中,可以用于线程也可以用于进程的同步和互斥

1.线程的同步

int sem_init(sem_t *sem,  int pshared,  unsigned int value);

int sem_wait(sem_t *sem);   //  P操作

int sem_post(sem_t *sem);  // V操作

例:

#define NUM 64
sem_t sem;
char buf[NUM];
void * writeBuff(void * arg){
	while(1){
		fgets(buf,NUM,stdin);
		sem_post(&sem);  //P操作
	}
}
void *readBuff(void * arg){
	while(1){
		sem_wait(&sem); //V操作
		printf("buf=%s\n",buf);
		memset(buf,0,NUM);
	}
}
int main(){
	
	pthread_t tid1,tid2;
	int re;
	sem_init(&sem,0,0);//初始化信号量
	re = pthread_create(&tid1, NULL,writeBuff, NULL);
	if(re!=0){
		printf("pthread_create:%s\n",strerror(re));
		exit(0);
	}
	re = pthread_create(&tid2, NULL,readBuff, NULL);
	if(re!=0){
		printf("pthread_create:%s\n",strerror(re));
		exit(0);
	}	
	while(1){
		sleep(1);
	}
	
}

2.线程的互斥

mutex互斥锁,和我们平时用的也没啥区别,也是一种信号量

任务访问临界资源前申请锁,访问完后释放锁

int  pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *  attr);

int  pthread_mutex_lock(pthread_mutex_t *mutex);申请锁

int  pthread_mutex_unlock(pthread_mutex_t *mutex);释放锁

FILE *fp;

pthread_mutex_t mutex;

void *write1(void* arg){
	int a=0;
    a = (int)arg;
	int len,i;
	char *c1 = "Hello world\n";
	char *c2;
	len = strlen(c1);
	int td = pthread_self();
	pthread_detach(pthread_self());
	c2 = c1;
	while(1){
	   
	   pthread_mutex_lock(&mutex);//上锁
	   for(i=0;i<len;i++){
		  fputc(*c1,fp);
		  fflush(fp);
		  c1++;
		  usleep(10000);
	   }
	   pthread_mutex_unlock(&mutex);//解锁
	   c1 = c2;
	   sleep(1);
	}
}

void *write2(void* arg){
	int a=0;
    a = (int)arg;
	int len,i;
	char *c1 = "How are your\n";
	char *c2;
	c2 = c1;
	len = strlen(c1);
	int td = pthread_self();
	pthread_detach(pthread_self());
	while(1){
	   
	   pthread_mutex_lock(&mutex);//上锁	
	   for(i=0;i<len;i++){
		  fputc(*c1,fp);
		  fflush(fp);
		  c1++;
		  usleep(10000);
	   }
	   pthread_mutex_unlock(&mutex);//解锁
	   c1 = c2;
	   sleep(1);	
	}

}

int main(){
	int re,i=0;
	pthread_t tid1,tid2;
	
	fp = fopen("1.txt","w");
	if(!fp){
		perror("fopen");
		return -1;
	}
	pthread_mutex_init(&mutex,NULL);
	
	re = pthread_create(&tid1, NULL,write1, (void *)i);
	pthread_detach(tid1);
	if(re!=0){
		printf("pthread_create:%s\n",strerror(re));
		exit(0);
	}
	re = pthread_create(&tid2, NULL,write2, (void *)i);
	pthread_detach(tid2);
	if(re!=0){
		printf("pthread_create:%s\n",strerror(re));
		exit(0);
	}	
	while(1){
		sleep(1);
	}

}

注:我们在使用线程函数的时候要注意,我们要使用线程库,在编译的时候也要注意要链接上库,不然编译会报错

gcc -o pthread pthread.c -lpthread  详细的可以查看库的生成那一节

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

35.Linux应用层开发---线程 的相关文章

  • 几个VS/QT常见错误解决方法

    X86与X64冲突 问题 1 gt Qt5Widgetsd lib Qt5Widgetsd dll fatal error LNK1112 模块计算机类型 X86 与目标计算机类型 x64 冲突 解决方法 在Qt VS Tools里添加正确
  • NMAKE编译CTK

    NMAKE编译CTK 启动编译环境 从VC中启动命令行或通过VC提供的批处理启动命令行 xff0c 以能运行编译环境 如果装了多个VC版本 xff0c 注意使用想要的VC版本启动安装编译环境 外链图片转存失败 源站可能有防盗链机制 建议将图
  • VERILOG实现四位七段数码管显示

    filename dyp v author lyq Date 2016 3 2 9 36 Lattice XP2 17 DEMO BOARD 4位七段带小数点数码管显示控制模块 clk 50M d1 d4 d 7 dp d 6 0 ASCI
  • 网络编程一些重要的面试题

    为什么需要三次握手 xff1f 答 xff1a 三次握手的目的是 为了防止已经失效的连接请求报文段突然又传到服务端 xff0c 因而产生错误 xff0c 这种情况是 xff1a 一端 client A发出去的第一个连接请求报文并没有丢失 x
  • XILNIXSDK2018为FreeRTOS增加配置项的方法

    在安装目录下找到目录 xff1a SDK 2018 1 data embeddedsw ThirdParty bsp freertos10 xilinx v1 0 data 然后通过两个步骤来完成配置项的增加 1 编辑文件 freertos
  • STM32F系列USART的IDLE中断要注意了

    只是调用USART ClearITPendingBit之类的方法是清除不了中断标志的 xff0c 必须必须在调用USART GetITStatus之后调用 USART ReceiveData xff0c 因为IDLE被搞成了一个帧 xff0
  • STM32库USART_ITConfig的坑

    USART ITConfig只能使用一个中断标志 xff01 看看中断参数的定义 xff1a define USART IT PE uint16 t 0x0028 define USART IT TXE uint16 t 0x0727 de
  • 最强大易用的开源MODBUS库-YMODBUS,包含MASTER/SLAVE

    无论是MASTER或SLAVE xff0c 构建MODBUS应用都极其简单 xff0c 可通过设置Master为Slave的Player轻松实现MODBUS网关 项目使用C 43 43 11编写 xff0c 支持多线程 xff0c 可在WI
  • keil5 添加注释说明模板

    我们使用 Keil uvision5 编写代码时 xff0c 为了规范代码 xff0c 一般会在文件开头对本文件进行注释说明 xff0c 同时我们也会在函数的开头对函数进行说明 但 Keil5 集成开发环境中没有这些注释模板 xff0c 而
  • Putty 使用记录

    Putty 显示时间戳 需要三个软件 Putty xff0c ExtraPuTTY xff0c mtputty Putty用来提供基本功能 ExtraPuTTY用来提供时间戳功能 mtputty用于多链接多页面显示 ExtraPuTTY中的
  • 学习java方面的一点收获

    学习JAVA方面的收获 经过将近两年的时间学习java xff0c 觉得在java方面有比较大的收获 在学习和实践过程中逐渐对代码习惯 软件思维都有比较进一步的了解 java语言的纯面向对象 平台无关性是java能够得到比较多的程序开发者的
  • ROS使用catkin_make编译指定功能包

    指定要编译的功能包 xff08 多个用分号相隔 xff09 catkin make DCATKIN WHITELIST PACKAGES 61 34 需要单独编译的包名 34 但是如再次使用catkin make编译所有功能包时会出现仅仅只
  • python中_、__、__xx__(单下划线、双下划线等)的含义

    默认情况下 xff0c Python中的成员函数和成员变量都是公开的 相当于java中的public xff0c 或者OC中定义在 h文件中的公开成员变量 在python中没有public private等关键词来修饰成员函数和成员变量 为
  • 龙芯1B核心板使用alsa音频播放设置,aplay播放

    龙芯1B核心板是默认启用alsa音频工具的 只需要进行一些配置就能使用 1 先检查你的板子的alsa工具是否正常 aplay l 可以查看 xff0c 是否已正确安装音频驱动 如果正常 xff0c 能看到你的音频驱动的信息 可能会出现 xf
  • centos 64bit安装arm-none-linux-gnueabi交叉编译工具链

    xfeff xfeff yum install glibc i686在centos中安装arm none Linux gnueabi有两种方法 xff0c 一种是apt get 安装容易但是不易成功 xff0c 一种是下载压缩包或安装程序
  • 旋转矩阵和欧拉角

    欧拉角介绍 旋转可以参考两种坐标系 内部坐标系 XYZ 角度 外部坐标系 xyz 角度 不考虑参考坐标系情况下 按照旋转方式可以分为两种 Proper Euler angles z x z x y x y z y z y z x z x y
  • SIP 鉴权 & HTTP 认证

    sip 鉴权是基于摘要签名认证的 具体来说 每一个用户都有一个用户名和密码 用户名和密码在客户端和SIP 服务器的数据库中都有保存 在认证的过程中 客户端将自己的信息 用户名 密码 url 等信息 做一些复杂的MD5 或者SHA256 SH
  • ROS——TF坐标变换

    TF功能包 创建功能包 cd catkin ws src catkin creat pkg learning tf roscpp rospy tf turtlesim 如果此时依赖包已有tf xff0c 后文中CMakeLists文件中的f
  • Gazebo——仿真平台搭建(基于Ubuntu20.04)

    目录 Gazebo安装配置 创建仿真环境 仿真使用 Rviz查看摄像头采集的信息 Kinect仿真 问题解决 xff1a 1 gazebo SpawnModel Failure model name mrobot already exist
  • 单片机要学多久可以找到工作?能找到哪类的工作

    单片机学多久能工作 单片机学好了能应聘什么工作 xff1f 从事单片机开发10年 xff0c 我见证了这个行业的成长 xff0c 最明显的就是这几年的工资涨幅 大家好 xff0c 我是小哥 xff0c 10年前我还是个对前景充满憧憬的小屌丝

随机推荐

  • 互联网企业部分面试笔试真题以及考察知识点总结(一)

    1 static的作用 1 1用static关键字修饰的静态变量 静态变量属于类 xff0c 在内存中只有一个复制 xff0c 只要静态变量所在的类被加 载 xff0c 这个静态变量就会被分配空间 1 2 static成员方法 Java中提
  • 史上最全网址导航大全,让世上没有找不到的好东西

    收录的导航网址大全 好用和常用的网址几乎都在里面 个人喜欢往浏览器书签收藏夹里塞喜欢的干货和网站 xff0c 以至于收藏夹里有着几千条网址 xff0c 所以比较喜欢导航 xff0c 但是浏览器原生自带的导航又太low 所以一般自己设置打开浏
  • HTTP的认证方式之DIGEST 认证(摘要认证)

    核心步骤 xff1a 步骤 1 xff1a 请求需认证的资源时 xff0c 服务器会随着状态码 401Authorization Required xff0c 返回带WWW Authenticate 首部字段的响应 该字段内包含质问响应方式
  • 相机标定评价标准

    相机标定的实验一般根据图像数据的类型分为两种 xff1a 1 仿真实验 2 实际场景的操作性实验 目前为止 xff0c 还没有形成一套完善的用于评价相机标定方法的标准体系 xff0c 通常采用的评价准则如下 xff1a 1 标定方法是否具有
  • ubuntu下串口工具的安装与使用

    1 概述 作为一个嵌入式开发人员 xff0c 串口是开发过程中不可或缺的工具之一 xff0c window下有各种各样的串口工具 xff0c 使用起来很方便 xff0c 这里不再做过多陈述 xff0c 这里主要介绍Ubuntu 16 04
  • Ubuntu查看文件大小或文件夹大小

    Ubuntu查看文件大小或文件夹大小 一 查看文件大小 查看文件大小的命令 xff1a ls l filename 会在终端输出 xff1a rw r r 1 root root 2147483648 Mar 5 09 39 filetem
  • 结构体数据对齐

    结构体数据对齐 结构体数据对齐 xff0c 是指结构体内的各个数据对齐 在结构体中的第一个成员的首地址等于整个结构体的变量的首地址 xff0c 而后的成员的地址随着它声明的顺序和实际占用的字节数递增 为了总的结构体大小对齐 xff0c 会在
  • 2016你配得上更好地自己

    传统里我一直觉得过完春节才是一年结束的时候 xff0c 但是现在慢慢习惯阳历的计算 xff0c 2017年1月1日 xff0c 看着空间里面新年祝福和期待 xff0c 突然觉得这才是过年 2016年就这样走了 xff0c 以后我再也回不到2
  • 树莓派镜像备份与恢复文章

    在做完下属步骤以后 xff0c 需要考虑分区表 xff0c 将分区表复制到镜像里 xff0c 否则系统无法启动 xff0c 而且还要回利用gparted dev loop0以及fdisk l dev loop0等命令 xff0c 查看分区类
  • 在树莓派上将现有系统复制到新存储卡(转载 )

    在树莓派上将现有系统复制到新存储卡 xff08 转载 xff09 http www eeboard com bbs thread 39663 1 1 html 最初 xff0c 使用树莓派的时候 xff0c 也许也只是为了新鲜 xff0c
  • 【c/c++】单链表、头指针、头结点、首元节点

    链表中第一个结点的存储位置叫做头指针 xff0c 那么整个链表的存取就必须是从头指针开始进行了 之后的每一个结点 xff0c 其实就是上一个的后继指针指向的位置 这里有个地方要注意 xff0c 就是对头指针概念的理解 xff0c 这个很重要
  • VINS-mono学习总结

    Vins mono是一个后端基于非线性优化的 单目与IMU紧耦合的融合定位算法 整体 xff1a 1 预处理模块 视觉 xff1a 特征点提取与追踪 IMU xff1a 惯性解算与误差状态分析 计算预积分量 2 初始化模块 xff08 旋转
  • Fast-lio个人总结

    Lidar第一帧作为基坐标 1 lidar原始数据预处理默认不提取特征 xff0c 对原始数据间隔式 xff08 间隔3个点 xff09 降采样提取 2 imu初始化 惯性解算 误差分析 状态 协方差预测 3 Lidar与imu时间状态对齐
  • 在rviz中使用键盘控制burger

    启动语句 roslaunch turtlebot3 fake turtlebot3 fake launch 启动rviz 话题通信 roslaunch turtlebot3 teleop turtlebot3 teleop key laun
  • shell脚本中=左右的空格问题

    赋值语句等号两边不能有空格 xff1a i 61 1 或i 61 i 43 1 而字符串比较 xff0c 等号两边必须有空格 if a 61 b 比较时 xff0c if a xxx b 中括号前后一定要加空格否则会报错xxx 61 eq
  • freertos.axf: Error: L6218E: Undefined symbol xTaskGetSchedulerState (referred from delay.o).

    今天移植了一下FreeRTOS xff0c 出现了freertos axf Error L6218E Undefined symbol xTaskGetSchedulerState referred from delay o xff0c 这
  • vnc桌面配置及黑屏问题解决

    一 vnc桌面配置 登入需要远程帐号下修改 vnc xstartup 如配置root远程桌面 vi vnc xstartup 原内容如下 xff1a xff3b x etc vnc xstartup xff3d amp amp exec e
  • 华清嵌入式--入学篇

    当初在学习嵌入式的时候 xff0c 就知道嵌入式门槛高 xff0c 需要的知识比较多 工作了4年多时间 xff0c 确实感觉还是刚入门的感觉 xff0c 焊接 调试 原理图 PCB 模电 数电 c语言 数据结构 单片机 linux等知识比价
  • 20.华清嵌入式--数据结构入门

    从今天开始正式开始学习数据结构与算法 从今天开始正式开始学习数据结构与算法 从上面的框图也可以从整体上把握数据结构的关键知识点 xff0c 不管是简单的顺序表还是栈 xff0c 树等 xff0c 学习的方法都是一样的他们的操作也都是无非都是
  • 35.Linux应用层开发---线程

    一 线程的概念和使用 1 线程的概念 为了进一步减少处理器的空转时间 xff0c 支持多处理器以及减少上下文切换开销 xff0c 进程在演化中出现了另外一个概念线程 它是进程内独立的一条运行路线 xff0c 是内核调度的最小单元 xff0c