线程安全:
- 为什么有线程安全,而进程没有?
因为进程是独立的,所以进程间不会相互影响,而一个线程可能会影响其他线程。多个线程因为临界资源的争抢写入操作会导致程序逻辑的混乱和数据二义性,因此就引入了线程安全的概念。
线程互斥:
实现:互斥锁 ---- 实际是一个计数器,但只有0/1.。它是为保证数据同一时间唯一访问性。
0 ---- 表示没有资源,不能操作(阻塞/报错返回)
1 ---- 表示有资源,能操作
特性:
1. 互斥锁不仅保证别的临界资源,自己也是临界资源。
2. 为保证自己安全,所以互斥锁是原子性操作。
3. 它将多线程并行操作改为串行操作,所以它会降低性能。
- 为什么互斥锁没有时序性?
因为:没有条件判断; 没有通知等待在条件变量上的线程。
互斥量实现原理:
- i++,++i 都不是原子的,它有可能被切出去,有可能会有数据一致性问题;
- 为实现互斥锁操作,操作系统提供了swap 和 exchange指令, / / exchange保证mutex只有一个锁
作用:将寄存器和内存单元的数据交换它。 只有一条指令,所以是原子性。
互斥锁实现:
创建互斥锁:
pthread_mutex_t lock;
初始化互斥锁:
pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); // 动态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 静态分配
加锁:
计数位1,则置零,继续操作;计数位为0则阻塞/报错返回。
pthread_mutex_lock(pthread_mutex_t *mutex); // 阻塞加锁
pthread_mutex_trylock(pthread_mutex_t *mutex); // 非阻塞加锁
pthread_mutex_timedlock(pthread_mutex_t *_restict_mutex); // 限时阻塞加锁
解锁:解锁操作 ---> 计数器置1.
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
死锁:一个程序一直获取不到锁,因此一直处于卡死状态就称为死锁。
产生死锁的四个必要条件:
1. 互斥条件 : 一个资源每次只能被一个执行流执行;
2. 不可剥夺条件 : 一个执行流拿到一个锁,只能由该执行流去释放锁;
3. 请求与保持条件: 一个执行流拿到一个锁,再去请求第二个锁时,不释放第一个;
4. 环路等待条件 : 两个执行流相互等待对方的锁。
避免死锁:
1. 破坏死锁的必要条件;
2. 加锁顺序一致;
3. 避免锁未释放的场景;
4. 资源一次性分配 。
销毁互斥锁:
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
/*
销毁互斥量需要注意:
1. 使⽤ "PTHREAD_ MUTEX_ INITIALIZER" 初始化的互斥量不需要销毁;
2. 不要销毁⼀个已经加锁的互斥量;
3. 已经销毁的互斥量,要确保后⾯不会有线程再尝试加锁。
*/
- 互斥量mutex:
pthread_mutex_t |
pthread_mutex_t lock |
创建互斥锁 |
pthread_mutex_init |
pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; |
初始化 |
pthread_mutex_lock |
pthread_mutex_lock(pthread_mutex_t *mutex); |
阻塞加锁 |
pthread_mutex_trylock |
pthread_mutex_trylock(pthread_mutex_t *mutex); |
非阻塞加锁 |
pthread_mutex_timedlock |
pthread_mutex_timedlock(pthread_mutex_t *_restict_mutex); |
限时阻塞加锁 |
pthread_mutex_unlock |
pthread_mutex_unlock(pthread_mutex_t *mutex); |
解锁 |
pthread_mutex_destroy |
pthread_mutex_destroy(pthread_mutex_t *mutex); |
销毁 |
线程同步:
实现:条件变量 --- 让数据的访问更加具有时序的可控性
- cond:条件变量
pthread_cond_t cond |
pthread_cond_t cond cond |
创建条件变量 | |
pthread_cond_init |
pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; |
初始化变量条件 | |
pthread_cond_destroy |
pthread_cond_destroy(pthread_cond_t *cond); |
销毁 | |
pthread_cond_wait |
pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); |
死等 |
死等之前需要解锁,并且解锁和死等是原子操作,被唤醒后需要加锁。但这个锁是非阻塞的,如果有多个线程时,就需要循环判断。 |
pthread_cond_timedwait |
pthread_cond_wait (pthread_cond_t *restrict cond, pthread_mutex_ *restrict mutex); |
限时等待 | |
pthread_cond_signal |
pthread_cond_signal(pthread_cond_t *cond); |
单个唤醒,在等待队列的线程 | |
pthread_cond_broadcast |
pthread_cond_broadcast(pthread_cond_t *cond); |
广播 |
通常在多对多情况下,为了防止出现唤醒错误,需要两个条件变量。