线程安全
线程安全的实现:同步 互斥
互斥:同一时间只能有一个线程能够访问资源实现资源访问的安全性
同步:通过条件判断实现线程对临界资源访问的合理有序
互斥:
互斥锁(通过互斥锁保护线程对临界资源的访问操作不会被打断)
原理:标记临界资源的两种状态(可访问/不可访问)在线程访问临界资源之前,先进行互斥锁加锁操作 在线程访问临界资源完毕后,进行解锁操作
互斥锁本身计数器的操作是原子操作
- 定义互斥锁变量:pthread_mutex_t mutex;
- 初始化互斥锁:pthread_mutex_t mutex=PHREAD_MUTEX_INITIALIZER
int pthread_mutex_init(pthread_mutex_t * mutex,pthread_mutexattr_t * attr);
- 在访问临界资源之前进行加锁
int pthread_mutex_lock(pthread_mutex_t * mutex); 阻塞
int pthread_mutex_trylock(pthread_mutex_t * mutex); 非阻塞
- 在访问临界资源完毕后解锁
int pthread_mutex_unlock(pthread_mutex_t * mutex);
- 不再使用互斥锁,则销毁
int pthread_mutex_destory(pthread_mutex_t * mutex);
应用:黄牛抢票,创建四个黄牛线程 共抢100张票
加入互斥锁后发现,虽然抢票安全,不会出现越界,但存在一定的不合理性,互斥是无法保证对资源访问的合理有序性
分析主要原因是,黄牛抢票入口函数中sleep函数导致在获取到锁后需要运行一定时间,此时尚未解锁,即使时间片轮转到其余三个线程 但无法获取锁处于阻塞状态,当时间片再次轮转到该线程,该线程执行完毕解锁,但由于while循环,在时间片还没结束的情况下再次获取到锁,导致始终一个线程获取到锁。
死锁
程序因流程无法继续推进卡死的情况
产生死锁的四个条件:
- 互斥条件,同一时间只有一个线程能够访问资源
- 不可剥夺条件 我加的锁只有我能解
- 请求与保持条件 得到A锁然后请求B锁,请求不到B,也不释放A
- 环路等待条件:A拿到1请求2 锁 B拿到2请求1锁
预防死锁:(破坏死锁产生条件)
1,保证加/解锁顺序一致
2,使用非阻塞加锁
避免死锁:银行家算法
同步
条件变量:一个pcb等待队列+使线程阻塞以及唤醒接口
原理:当线程获取资源不合理的时候,调用阻塞接口,使线程阻塞,将pcb挂到等待队列,等待资源访问合理的时候通过唤醒接口唤醒阻塞的线程
注意:条件变量自身只提供阻塞和唤醒接口,获取资源是否合理需要程序员自己判断
- 定义条件变量:pthread_cond_t cond;
- 条件变量初始化:
pthread_cond_t cond=PHREAD_COND_INITIALIZER
int pthread_cond_init(pthread_cond_t * cond,pthread_condattr_t * attr);
- 在获取资源不合理时调用阻塞接口使线程阻塞
int pthread_cond_wait(pthread_cond_t * cond,pthread_mutex_t * mutex);只要调用就阻塞 没有唤醒则一直等待
int pthread_cond_timedwait(pthread_cond_t * cond,pthread_mutex_t * mutex,struct timespec* abstime);等待一定时间后自动唤醒
- 其他线程促使资源获取合理后,唤醒等待队列上的线程
int pthread_cond_signal(pthread_cond_t * cond);唤醒一个
int pthread_cond_broadcast(pthread_cond_t * cond) 唤醒所有
- 销毁
int pthread_cond_destory(pthread_cond_t * mutex);
应用:厨师-顾客创建两个线程,以柜台上饭的数量作为临界资源
厨师接口:
- 加锁,防止在访问条件变量时其他线程同时会访问
- 条件变量进行判断,根据临界资源,是否有饭作为判断条件,对于厨师来说,有饭则不需要做饭则阻塞,没饭则做饭
- 做饭使得临界资源数量+1,可以唤醒顾客吃饭
- 解锁
顾客接口:
- 加锁,防止在访问条件变量时其他线程同时会访问
- 条件变量进行判断,根据临界资源,是否有饭作为判断条件,对于顾客来说,没有饭则阻塞,有饭则吃饭
- 吃饭使得临界资源数量+1,可以唤醒厨师做饭
- 解锁
注意:1条件判断应该使用while循环
原因分析:如果存在多个顾客情况,开始没有饭,第一个顾客发现没有饭阻塞解锁,其余顾客拿到锁进入也因为没有饭而阻塞,导致多个顾客均阻塞,当时间片轮转,厨师拿到锁做了一碗饭,开始唤醒顾客,多个顾客都被唤醒,但只有其中一个顾客拿到了锁,其他顾客因抢不到锁而阻塞,等待这个顾客吃完饭解锁后,可能会因为时间片轮转,拿到所得不是厨师而是其余顾客,如果没有二次条件判断就会出现在没有临界资源(没有饭)情况下吃到饭。
2在具有多个角色的情况下,应该使用多个条件变量
原因分析:多个顾客,因为没有饭吃,被挂载在阻塞队列上,一个厨师做好饭,唤醒一个顾客,顾客吃完饭唤醒阻塞队列上的线程,但因为顾客在前,厨师在后,有可能再次唤醒的不是厨师而是顾客,该顾客会因为二次条件判断没有饭再次阻塞,对此需要根据各自创建不同的条件变量,从而有不同的阻塞队列。
3条件判断会对临界资源进行访问 ,需要搭配互斥锁使用
生产者与消费者模型
应用于大量数据产生及处理的场景
优点:解耦合(各自完成各自功能) 支持忙闲不均 (满足同步互斥) 支持并发(缓冲区必须实现线程安全)
- 生产者之间保持互斥关系
- 消费者之间保持互斥关系
- 生产者与消费者之间保持同步与互斥关系
生产者消费者模型,在大量数据需要处理的情况下所产生的一种应用场景,需要借助一个缓冲区来实现,这个缓冲区就是阻塞队列BlockQueue
- 创建两种角色的线程,各自负责数据的产生与消费功能
- 线程安全的任务数据队列 ,向外提供入队与出队操作
线程安全的任务数据队列通过class BlockQueue 阻塞队列实现:
- 有空闲节点可以入队数据,没有空闲节点则阻塞入队线程
- 有数据节点可以出队数据,没有数据节点则阻塞出队线程