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

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.dtcms.com/a/289493.html

相关文章:

  • 【网络编程】网络传输-JSON
  • 【C语言】字符串与字符函数详解(下)
  • Shell脚本-cut工具
  • 从零到一MCP快速入门实战【1】
  • 疯狂星期四第13天运营日报
  • Java拓扑排序:2115 从给定原材料中找到所有可以做出的菜
  • Linux 基本指令详解
  • Self-Consistency:跨学科一致性的理论与AI推理的可靠性基石
  • WebDriver 对象中的方法
  • C++STL系列之list
  • Vue DIY 内容文本超出组件
  • Numpy库,矩阵形状与维度操作
  • 矩阵算法题
  • ZYNQ创新实践:免IIC驱动直控MCP4661T数字电位器
  • ollama基本配置
  • 仙盟数据库应用-外贸标签打印系统 前端数据库-V8--毕业论文-—-—仙盟创梦IDE
  • 数据库操作丨C++ 操作 数据库——SQLServer 篇
  • 数据库技术总结
  • 激光雷达和相机在线标定
  • 试用SAP BTP 06:AI服务-Data Attribute Recommendation
  • Java行为型模式---解释器模式
  • 30天打牢数模基础-XgBoost讲解
  • 第四章第一节 OLED 调试工具
  • 【LeetCode 热题 100】200. 岛屿数量——DFS
  • 20250720-3-Kubernetes 调度-资源限制对Pod调度的影响(2)_笔记
  • 隧道无线调频广播与“群载波”全频插播技术在张石高速黑石岭隧道中的应用
  • 数据结构第二章:线性表之顺序表
  • Kubernetes (K8S)知识详解
  • 【k8s集群管理平台】k8s运维管理的新玩法,让运维电脑随时不离身的现状成为过去
  • 【论文研读】SlowFast Networks for Video Recognition