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

Linux:线程控制

线程概念

线程(Thread)是进程(Process) 中的一个执行单元,是操作系统能够进行运算调度的最小单位。

线程也被称为“轻量级进程”(Lightweight Process, LWP)。

一个进程可以包含多个线程,这些线程共享该进程所拥有的全部资源(后续展开解释)。

轻量级进程

为什么认为线程是轻量级进程,主要有两方面:

1.线程创建:创建线程不产生新的进程地址空间,也就不需要创建对应的页表

2.线程切换:

(1)由于进程地址空间不同,进程切换时无法从高速缓存中读取数据,只能从较慢的内存中读取数据,而线程由于共享地址空间,更有可能直接从缓存中读取数据。

(2)由于进程地址空间不同,进程切换时会导致TLB失效,不能直接通过较快的TLB获得物理地址,而是需要通过较慢的页表来获得物理地址,而进程由于共享地址空间,即使在缓存中读取不到数据,也更有可能通过高速的TLB来获得物理地址进而访问内存

(3)相较于进程切换,线程切换时需要保存和恢复上下文更少

Linux下的线程

Linux下并没有为线程设计独立的概念和数据结构(即没有与PCB相对的TCB),线程和进程都是通过task_strcut来进行管理和调度的,线程和进程也都是通过同一个底层系统调用 clone() 来创建的。

不过为了内核对线程进行调度,线程有用于标识自身唯一性的ID:LWP(类型为pid_t)

使用命令ps -aL可以看到线程ID:

使用函数gettid()也可以获取线程ID:

线程资源共享

1.cpu分配给进程的时间片会均分给每一个线程

2.每个线程都有和进程一样的虚拟地址空间(这意味着理论上线程可以访问进程所有的代码和数据)。

3.此外,线程还共享⽂件描述符表 ,每种信号的处理⽅式,当前⼯作⽬录 ,⽤⼾id和组id。

线程独立

线程之间共享进程的所有资源,但为了各自完成不同的任务,还需要使得线程之间在一定程度上相互独立,这就需要每个线程持有一部分私有内容来标识自身唯一性并独立完成任务。

因此,每个线程拥有独立的:

线程ID,用于标识自身唯一性

信号掩码

调度策略和优先级

错误码

线程执行上下文,包括:

        程序计数器 (Program Counter): 指示当前执行指令的位置。

        寄存器集合 (Register Set): 存储线程运行时的临时数据。

线程栈 (Stack): 用于存储函数调用时的局部变量、参数、返回地址等。每个线程的栈是私有的,这是保证线程独立执行的关键。

线程局部存储(TLS):用于存储只能被该线程访问的全局变量

维护线程独立

pthread库通过数据结构struct_pthread来维护线程独立,struct_pthread位于共享区

而线程id就是该线程对应的struct_pthread的首地址(该线程id用于调用其他的线程API,属于进程级,并不是内核级的、用于标识线程唯一性的线程id),类型为pthread_t

线程栈

进程在创建时会复制父进程的栈区的地址空间,在使用时可以进行写时拷贝和动态增长 。

但由主线程⽣成的⼦线程,它的栈将不再是向下⽣⻓的,⽽是事先在共享区占用一个固定大小的空间。

线程局部存储

在进程内的全局变量被所有线程共享,有时我们希望一个全局变量被每个线程持有一个副本,这时就需要__thread来修饰该变量,此时该全局变量被每个线程独立地访问和操作

但是要注意:__thread只能用来修饰内置类型,不能用来修饰自定义类型。

编写如下代码进行测试:

​#include<pthread.h>
#include<unistd.h>
#include<iostream>__thread int v=100;void* task(void* arg)
{v+=(long long)arg;std::cout<<"线程"<<gettid()<<"的v是"<<v<<std::endl;return 0;
}int main()
{pthread_t t1,t2;pthread_create(&t1,NULL,task,(void*)1);  pthread_create(&t2,NULL,task,(void*)2);pthread_join(t1,NULL);pthread_join(t2,NULL);
}

输出结果:

可以看到两个线程对全局变量的操作互不干扰

Linux线程控制

线程创建

thread:返回线程ID

attr:设置线程的属性,attr为NULL表⽰使⽤默认属性

start_routine:是个函数地址,线程启动后要执⾏的函数

arg:传给线程启动函数的参数

返回值:成功返回0,失败返回错误码

线程终止

终止当前线程:

retval:用于输出线程要执行的函数的返回值

终止指定线程:

返回值:成功返回0,失败返回错误码

终止某一线程的三种方法:

1.从线程函数return。这种⽅法对主线程不适⽤,从main函数return相当于调⽤exit。

2. 线程可以调⽤pthread_ exit终⽌⾃⼰。

3. ⼀个线程可以调⽤pthread_ cancel终⽌同⼀进程中的另⼀个线程。

线程等待

主线程等待指定线程结束

thread:用于指定线程

retval:用于传出线程执行函数的返回值

线程分离

线程有两种状态:joinable和detached

默认情况下,新创建的线程是joinable的,线程退出后,需要主线程调用pthread_join,否则⽆法释放资源,从⽽造成系统泄漏。

如果不关⼼线程的返回值,可以调用pthread_detach,当线程退出时,⾃动释放线程资源。

适用于主线程为死循环的情景

thread:用于指定线程

注意:pthread_join和pthread_exit传参时不要传局部变量给retval,因为线程返回的值必须在线程结束后仍然有效。

多线程优缺点

优点:

1.创建与切换开销低
2.在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
3.对于计算密集型应用,为了能在多处理系统上运行,将计算分解到多个线程中实现
4.对于I/O 密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

缺点:
1.性能损失
一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有额外的同步和调度开销。
2.健壮性降低
线程共享数据容易引发线程安全问题。
单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随之崩溃。
3.缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
4.编程难度提高
编写与调试一个多线程程序比单线程程序困难得多  

http://www.xdnf.cn/news/16029.html

相关文章:

  • 基于SpringBoot+MyBatis+MySQL+VUE实现的医疗挂号管理系统(附源码+数据库+毕业论文+答辩PPT+项目部署视频教程+项目所需软件工具)
  • LeetCode 刷题【8. 字符串转换整数 (atoi), 9. 回文数】
  • 学成在线项目
  • 手推OpenGL相机的正交投影矩阵和透视投影矩阵(附源码)
  • Unity 新旧输入系统对比
  • 开发工具缓存目录
  • Redis通用常见命令(含面试题)
  • [数据库]Neo4j图数据库搭建快速入门
  • 设备健康管理实施案例:中讯烛龙预测性维护系统的实战应用
  • 基于bert-lstm对微博评论的情感分析系统设计与实现
  • 新版 Java SE 集合框架 Map 篇
  • Pycharm的Terminal打开后默认是python环境
  • Kafka 在分布式系统中的关键特性与机制深度解析
  • 基于Pytorch的人脸识别程序
  • 1948. 删除系统中的重复文件夹
  • 定点小数与分数
  • langchain调用本地ollama语言模型和嵌入模型
  • 线程状态线程安全
  • gradle微服务依赖模版
  • 软件反调试(5)- 基于注册表实时调试器检测
  • [Python] -项目实战7- 用Python和Tkinter做一个图形界面小游戏
  • 我的世界-推理
  • 基于Event Sourcing和CQRS的微服务架构设计与实战
  • 连接语言大模型(LLM)服务进行对话
  • 随着GPT-5测试中泄露OpenAI 预计将很快发布 揭秘GPT-5冲击波:OpenAI如何颠覆AI战场,碾压谷歌和Claude?
  • [硬件电路-58]:根据电子元器件的控制信号的类型分为:电平控制型和脉冲控制型两大类。
  • 威力导演 12:革新级影音创作平台——专业特效与极致效率的完美融合
  • 算法题(176):three states
  • 100个GEO基因表达芯片或转录组数据处理27 GSE83456
  • [simdjson] 实现不同CPU调度 | 自动硬件适配的抽象