1、线程的概念:传统操作系统下,进程是一个pcb,操作系统通过控制pcb控制程序的运行,并没有线程概念;但是在Linux下,线程是通过pcb来模拟实现的,所以此时可以说线程是一个pcb,又称为轻量级进程,此时进程是线程组。
进程是资源分配的基本单位,线程是CPU调度的基本单位
2、多线程与多进程任务处理优缺点对比
注:对主程序稳定性要求更高时优先选择多线程任务处理方式。
3、线程进程的共享与独立
共享 | 独立 |
---|---|
虚拟地址空间 | 栈 |
文件描述符表 | 寄存器 |
信号处理方式 | 信号屏蔽字 |
当前工作路径 | 线程标识符 |
用户id,组id | errno |
4、线程的相关操作
线程控制:操作系统并没有提供线程创建接口,因此创建或使用时都是调用库函数(创建的线程是一个用户态线程,但是在内核中对应有一个轻量级进程实现线程程序的调度)
线程创建:pthread_create
pcb->pid LWP 轻量级进程id
pcb ->tgid PID 进程id
tid 线程空间首地址
每一个线程都有自己独有的一块空间,并且这块空间在进程的虚拟地址空间上。
线程终止: 在任意进程中不能使用exit退出,因为exit退出的也是整个进程。
在普通线程中使用return退出,但是在main函数中不能使用return退出,因为退出的是整个进程。
pthread_exit 主动退出
pthread_cancel 线程被动退出
线程终止类似于进程终止,也会产生(僵尸线程)
线程等待:等待指定线程的退出,获取指定线程的退出返回值,回收退出线程的资源
线程创建成功后,默认有一个joinable属性,处于joinable属性的线程退出时(资源不会自动回收)才能被等待,也必须要被等待。
线程分离:分离线程的属性,从joinable 设置为detach属性
处于detach属性的线程退出后自动回收资源,不需要被等待
分离线程前提:用户对线程的退出返回值不关心。
pthread_detach
线程分离可以是任意线程在任意时刻进行分离(线程一旦创建直接分离,线程入口函数中分离自己)
线程安全:****多个线程对同一个临界资源进行了不受保护的非原子操作
线程安全就是多个线程对临界资源进行操作,而不会造成数据二义性
线程安全的实现:同步与互斥
5、同步与互斥:
同步:临界资源访问的时序可控(保证合理)
互斥:临界资源同一时间的唯一访问(保证安全)
互斥的实现:使用互斥锁(只具有0/1的计数器)
互斥锁使用步骤:
1、定义互斥锁变量类型 pthread_mutex_t
2、初始化互斥锁 pthread_mutex_init
3、加锁 pthread_mutex_lock
4、解锁 pthread_mutex_unlock
5、销毁 pthread_mutex_destroy
保护对临界资源的操作
死锁:多个线程抢夺锁资源,但是因为抢夺顺序推进不当引起死锁,导致程序卡死
产生死锁的四个必要条件:
互斥条件(我加了锁,别人不能加)
不可剥夺条件(我加的锁,别人不能解锁)
请求与保持条件(拿到第一个锁后去请求第二个锁,但是第二个锁获取不到,第一个锁也不释放)
环路等待条件
预防:破坏必要条件
避免:限时阻塞、银行家算法、死锁检测算法
同步的实现:使用条件变量实现等待与唤醒功能,操作资源的条件不满足则需要等待,其它线程促使条件马努后唤醒等待。
pthread_cond_t 定义
pthread_cond_init 初始化
pthread_cond_wait 等待 (包含了解锁、休眠、被唤醒后加锁三步原子操作)
pthread_cond_signal 唤醒
pthread_cond_destory 销毁
条件变量搭配互斥锁一起使用的,因为条件变量的实现同步只是提供了等待与唤醒功能,并没有提供条件判断的功能,因此条件判断需要用户实现,但是条件的操作是一个临界资源的操作,因此需要保护,需要在条件判断之后加锁
如果加锁成功后,因为条件不满足而陷入休眠,就会导致卡死,因此需要在休眠之前解锁,并且解锁与休眠必须是原子操作。
被唤醒之后,即将对临界资源进行操作,操作之前需要进行保护加锁
条件的判断需要循环进行 ---防止多次消费/多次生产,导致数据二义,逻辑混乱
不同的角色需要等待在不同的条件变量上 ————防止角色唤醒错误导致阻塞。