Linux--线程互斥与同步--0112 13
线程互斥
1.背景概念
临界资源:多线程执行流共享的资源就叫做临界资源。
临界区:每个线程内部,访问临界资源的代码就叫做临界区。
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区 ,对临界资源起保护作用。
原子性:可以被调度机制打断,但是操作只有两种情况,要么做完了,要么没做。
2. 互斥量(加锁)
//定义全局的一把锁 并初始化
pthread_mutex_t mtx=PTHREAD_MUTEX_INITIALIZER
//在临界区上锁
pthread_mutex_lock(&mtx);
//临界区结束解锁
pthread_mutex_unlock(&mtx);
注意:
加锁的范围一定要尽可能小。
全局变量的锁不需要销毁。
定义局部的一把锁
//初始化
pthread_mutex_init(&mtx,nullptr);
//上锁 和解锁相同
//释放
pthread_mutex_destroy(&mtx);
2.1 互斥量的相关问题
- 加锁之后,线程在临界区是否还能进行程序替换?
可以切换,但是是在自己持有锁的情况下进行的程序替换。其他线程如果要执行临界区的代码,依然需要申请锁,持有锁的线程不执行完毕解锁,其他线程无法进入临界区。这就保证了临界区中数据的一致性。
- 线程访问临界区一定需要先申请锁。
如果一个线程不申请锁就去访问临界资源,这就是一种错误的编码方式。
2.2 互斥量实现的原理
当线程A进行movb 时 al中放入了0,假设此时A线程的时间片到了,A就会拿着寄存器中A的上下文数据(0)结束调度。此时B进程进来,依然在进行movb时,将al放入0,然后继续执行第二步,xchgb,将mtx和al的值进行交换。此时al是1 mtx是0 。
进行判断,大于0,进入if语句。假设在return 0 这步,B线程的时间片到了,B线程就带着执行到的步数,以及自己的上下文数据走了。
A线程进入CPU,继续上次未执行的地方开始运行。xchgb 交换了0和 0 。
进行判断,不符合走入了else语句,进行阻塞等待。再被切换回来时,执行goto lock 语句,如果B线程执行完毕,将1给mutex,那么A线程在交换时,al就可以成为1,进而成功申请锁,不然就是上述进行重复。
3. 死锁
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。
3.1 造成死锁的原因
线程在访问临界区的时候,可能不止需要申请一把锁。假设有两个线程A和B,线程A需要先申请锁1,再申请锁2,线程B需要先申请锁2,再申请锁1。如果二者同时拥有自己需要申请的第一把锁,那么这个进程进入死锁。
死锁:持有锁的线程,在持有自身锁的情况下,向对方申请对方的锁,进而导致代码无法推进。
3.2 死锁的四个必要条件
互斥条件:一个资源每次只能被一个执行流使用。
请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:一个执行流获得的资源,在未使用完之前,不能强行剥夺。
循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系。
3.3 避免死锁
- 破坏上述必要条件。
- 加锁顺序保持一致。
- 临界区外及时释放锁。
- 资源一次性分配。(一次性访问临界资源,不要访问一次加一把锁)
4. 可重入与线程安全
4.1 联系
- 如果函数是可重入的,那该函数一定是线程安全的。
- 如果函数是不可重入的,那该函数有可能引发线程安全问题。
- 如果一个函数中有全局变量,那这个函数既不是可重入函数,也不是线程安全的。
4.2 区别
- 可重入函数是线程安全函数中的一种。
- 线程安全不一定是可重入的,而可重入的一定是线程安全的。
- 可重入函数加锁是线程安全的,但在锁未释放之前是不可重入的。
线程同步
按照一定顺序,进行临界资源的访问,称为线程同步。
因为时序问题,而导致程序异常,称为竞态条件。
当我们申请临界资源时,要先检测临界资源是否存在,但是做检测也是对临界资源的访问。这也处在加锁和解锁之间。如果使用这种方式,注定了我们一定会频繁的申请和释放锁。
所以我们需要其他的方式让线程检测到资源不就位的情况,在这种情况下,不再让线程频繁检测,等待。当资源就绪时,再通知对应的线程,让他们来进行资源申请和访问。
5.条件变量函数
5.1.1 初始化
- 如果是全局的或者静态的
pthread_cond_t cond =PTHREAD_COND_INITIALIZER;
- 局部的
//先定义一个
pthread_cond_t restrict;
//初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
局部的需要销毁
int pthread_cond_destroy(pthread_cond_t* cond);
参数介绍
cond : 要初始化的条件变量
attr :NULL
5.1.2 等待条件满足
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t* restrict cond,pthread_mutex_t* restrict mutex);
参数介绍
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释
5.1.3 唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_broadcast 把所有线程全部唤醒
pthread_cond_signal 唤醒对应的那个线程