线程安全
所谓线程安全,其实就是当多个线程对临界资源进行争抢访问的时,不会造成数据二义或者逻辑混乱的情况(通常情况下对全局变量和静态变量进行操作时在会出现)
常见的线程安全的情况:
- 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
- 类或者接口对于线程来说都是原子操作
- 多个线程之间的切换不会导致该接口的执行结果存在二义性
常见的线程不安全的情况:
- 不保护共享变量的函数
- 函数状态随着被调用,状态发生变化的函数
- 返回指向静态变量指针的函数
- 调用线程不安全函数的函数
线程安全的实现:
同步:通过条件判断实现对临界资源访问的合理性
互斥:通过同一时间对临界资源访问的唯一性实现临界资源访问的安全性
互斥
互斥:通过同一时间对临界资源访问的唯一性实现临界资源访问的安全性
如果需要实现互斥,就需要借助互斥锁
互斥锁:互斥锁本身是一个只有0和1的二进制计数器,描述了一个临界资源当前的访问状态,所有线程在访问临界资源前都需要先判断当前临界资源的状态是否允许访问,如果不允许则让线程等待,如果允许则让线程访问资源,但是访问资源时需要加锁修改临界资源的状态为不可访问,保证一次只能有一个线程访问临界资源,访问结束后再解锁,使临界资源恢复成可访问的状态。
互斥锁虽然本身也是一个临界资源,如果自己的操作都不安全,那怎么能保证其他资源的安全?所以它自身的加锁解锁操作保证了原子性。
所以互斥锁与CPU中寄存器进行数据交换,交换当前的状态,确保操作能够一次完成。
操作:
1.定义互斥锁变量:
pthread_mutex_t mutex;
2.初始化互斥锁:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr)
attr:互斥锁属性,如果为NULL则为默认属性
头文件:#include <pthread.h>
返回值:成功返回0,失败返回其他
3.访问临界资源之前加锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
//阻塞加锁,如果当前互斥锁已被锁定,则会阻塞当前线程,直到该互斥锁可以用
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//非阻塞加锁,如果当前互斥锁已被锁定,则不会阻塞,直接返回互斥锁状态
4.访问临界资源结束后解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
5.销毁互斥锁:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
死锁
死锁即当多个线程对锁资源争抢访问,但是由于推进的顺序不当,导致互相等待最终导致程序流程无法继续推进,导致了该资源处于永久等待状态,这就是死锁。
死锁产生的必要条件:
互斥条件:一个资源每次只能被一个执行流使用
请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系
死锁的预防:
1.加锁顺序一致
2.避免锁未释放的场景
3.资源一次性分配
4.避免死锁算法
避免死锁的算法:
死锁检测算法
银行家算法
(过段时间会写一个关于这两个算法的博客)
同步
同步:通过条件判断实现对临界资源访问的合理性
如果要实现同步,就需要借助条件变量
条件变量:线程在满足资源的访问条件的时候才去访问临界资源,否则就会挂起线程,直到条件满足才唤醒线程。
条件变量提供了是线程等待和唤醒的接口和一个线程的等待队列,所以需要我们自己来完成访问条件的判断。
操作:
1.定义条件变量:
pthread_cond_t cond
2.初始化条件变量:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t
*cond_attr)
//动态创建
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
//静态创建
3.使线程挂起:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
//条件等待
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
//计时等待,如果时间内没有完成条件则报错返回
4.唤醒线程:
int pthread_cond_signal(pthread_cond_t *cond);
5.销毁条件变量:
int pthread_cond_destroy(pthread_cond_t *cond)
为什么 pthread_cond_wait需要使用互斥锁呢?
- 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
- 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。