Linux线程概述

博客介绍了进程和线程的概念,阐述线程的状态、分离属性等。还说明了进程中多线程的共享资源和私有资源,介绍线程创建的 API。重点讲解了线程通信的信号量机制,包括 P 操作和 V 操作,以及同步机制和互斥机制,如互斥锁的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

@ TOC

概念

进程
1.进程有独立的地址空间
进程是系统为了执行一个程序而分配的资源的总称。
每个进程创建的时候,操作系统会为其分配相应的资源,包括内存资源、CPU资源等。
每个进程有自己独立的地址空间,其中代码、数据
每个进程只能访问自己地址空间中的代码和数据
每个进程除了存放代码和数据之外,还包括系统数,包括PCB(进程控制块)、各种寄存器的值、堆栈。

2.Linux 为每个进程创建 task_struct
对于 Linux 来说,每个进程会在内和空间创建一个结构体 task_struct (任务结构体)
这个结构体就是用来描述一个任务,其属性就存放在该结构体中,比如进程号、进程的状态,进程的优先级,进程打开的文件等

3.每个进程都参与内核调度,互不影响	
每个进程创建好以后都需要内核的调度,才能执行,并且一个进程结束了,并不会影响到其他进程的执行。
线程
进程在切换时系统开销比较大
为了匹配一个高速设备CPU和一个低速设备RAM, 在中间会加上 高速缓存 cache  和 转换检测缓冲区 TLB ( 一般也是通过静态缓存来实现的)
cache 的成本比较高,所以容量比较小

CPU 实际运作时会先将一批缓存加载到 cache 中,然后再从 cache 中去读取数据。如果 cache 中没有数据了, 则再去RAM中加载一批指令和数据。
cache 也分为指令 cache 和 数据 cache 

TLB 旁路转换缓冲/页表缓冲,其中每一行都保存着一个由单个PTE(Page Table Entry,页表项)组成的块。
页表是一种特殊的数据结构,放在系统空间的页表区,存放逻辑页与物理页帧的对应关系。 每一个进程都拥有一个自己的页表,PCB表中有指针指向页表

即每个进程都地址空间都不一样,访问的物理内存也不一样,所以会用到不同页表项

综上所述,如果CPU频繁的切换进程,cache 中的数据和 tlb 的缓存也都需要频繁的重新刷新,所以开销会比较大

所以,很多操作系统引入了轻量级进程LWP(线程)线程相比于进程会节省大量开销,因为同一进程中的线程共享相同地址空间,切换的时候 cache 和 TLB 不需要刷新或者只需要更新一小部分。
当然,不同进程间的进程互相切换也需要做同样工作。

对于 Linux 而言不区分进程、线程,都会创建一个 task_struct 来描述这个任务

总结

通常线程指的是共享相同地址空间的多个任务
使用多线程的好处在于大大提高了任务切换的效率,避免了额外的TLB & cache 的刷新

线程相比于进程可,资源的开销更小,可以更好的利用CUP的资源

原理:时间转轮算法

并发:看起来同时发生,实际一次只有一条指令执行
并行:同一时刻有多条指令在多个处理器上执行
同步:阻止依赖关系的系统调用同时发生
异步:任意两个彼此独立的操作是异步的,表现了事
	 件的独立性

线程的四种状态:

就绪:等待CUP资源
运行:执行过程中
堵塞:等待其他资源
终止:执行完成,或者被取消

线程的分离属性:

如果线程是非分离的,在线程结束时系统会保留他的系统资源和虚拟内存。有时可以被称为僵尸进程。
具有了分离属性,在线程解释的时候系统会自动释放掉未释放的系统资源。包括内存空间、堆栈、保存寄存器的内存空间。
注意在线程结束前要销毁互斥量。

头文件:

#include<pthread.h>

链接库:

-pthread
注意:exit ,_Exit,_exit 都是用来退出进程的。
共享资源

一个进程中的多个线程共享以下资源

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

每个线程私有的资源包括

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

创建线程:
pthread_t pthread_creat( pthread_t *restrict attr , const pthread_attr_t *restrict attr , void *( *start_routine )( void *) , void *restrict arg )

通信
线程共享同一进程的的地址空间,所以线程间的通信通过全局变量来交换数据实现的非常方便。
但如果多个线程对用一个数据的访问存在先后顺序,那么,就需要引入同步或者互斥机制

定义信号
sem_t sem

初始化
sem_init(&sem,0,0)
初始化什么信号
0是线程通信,非0是进程通信
初始化的值为0,相当于 sem=0

P操作,堵塞
sem_wait()

V操作,唤醒
sem_post()

头文件
#include<semap[hore.h>

使用了线程的话,注意编译时加上 -lpthread

只有信号值为0的时候才能堵塞

同步机制
多个任务按照约定的先后次次序相互配合完成一件事情
1968年,基于信号量的概念提出了一种同步机制,即信号灯。
由信号量来决定一个线程是继续运行还是堵塞等待

信号量(灯)
信号量代表某一类资源,其值表示系统中的该资源的数量,(因此必定是一个非负数)
信号量是一个后保护的变量,只能通过3中操作来访问

初始化
P操作(申请资源)
V操作(释放资源)

P操作的本质

if (信号量的值 > 0)
{
申请资源的任务继续运行;
信号量的值减一;
}
else
{
申请资源的任务堵塞。
}

V操作的本质

信号量的值加一;
if ( 有任务在等待资源)
{
唤醒等待的任务,让其继续运行
}

Posix 中定义了两类信号量
无名信号量 用于线程间通信,不会创建文件
有名信号量 用于进程间通信,需要创建文件

互斥机制

临界资源

是一种特殊的资源,一般来说临界资源是一种共享资源,能够被多个任务访问到。
比如全局的一些数组,缓冲区,变量等,都应当认为是临界资源
一次只允许一个任务(进程、线程)访问的资源。

否则可能造成数据的混乱。
比如一个任务读数据,还没有读完就被另一个任务写入了,就会出现错误。
临界资源不光包括数据,还包括硬件等

临界区
访问临界资源的代码叫做临界区,其他的代码叫做非临界区
只有实现临界区的互斥才能保护临界资源

互斥机制

互斥锁
mutex 互斥锁
互斥锁只能被一个任务所持有,即使此时其他任务此时申请这个锁,依旧无法获得这个锁,必须等到这个锁空闲,不被任何任务所持有时才能获得这个锁

利用互斥所实现临界区的互斥
任务访问临界资源之前申请锁,访问完后释放锁
只有申请到锁的任务才能执行,其他没有申请到锁的任务都要堵塞,直到持有锁为止

锁的操作包括 初始化锁、申请锁和释放锁
如果临界区同时访问多个临界资源,一个互斥所就可以了,如果是不同的时候访问临界资源,则需要两个锁,这是一些最基本的原则

头文件
#include<pthread.h>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值