一.线程安全
概念:多个线程同时对临界资源进行访问,不会造成数据二义问题
实现:同步+互斥
同步:对临界资源访问的时序合理性
互斥:对临界资源同一时间访问的唯一性
线程间互斥的实现:互斥锁mutex
pthread_mutex_t mutex; //定义互斥锁变量
pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict 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_destroy(pthread_mutex_t *mutex); //销毁
死锁:多个线程对资源进行竞争访问,但因推进顺序不当,导致相互等待,程序无法往下运行
死锁产生的必要条件
1.互斥条件:一个锁只有一个线程可以获取
2.不可剥夺条件:A加的锁,B不能解
3.请求与保持条件:A请求B,但获取不到,A也不释放已获得的资源
4.环路等待条件:A请求B,同时B请求A
避免死锁
1.破坏四个必要条件
2.加锁顺序一致
3.避免锁未释放的场景
4.资源一次性分配
二.线程同步
实现:等待+唤醒
操作条件不满足则等待,条件满足则唤醒
条件变量
条件变量实现同步:线程在对临界资源访问之前,先判断是否能够进行操作,若可以则直接操作,若不能,则条件变量提供等待功能,其他线程促使操作条件满足,然后唤醒条件变量等待队列上的线程
pthread_cond_t cond; //定义条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr); //条件变量初始化
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex); //用户在判断条件不满足的情况下提供等待功能
int pthread_cond_signal(pthread_cond_t *cond); //用户在促使条件满足后,唤醒等待队列上的线程
int pthread_cond_destroy(pthread_cond_t *cond); ///销毁
三.生产者与消费者模型
一个场景、两种角色、三种关系
解决问题:解耦和、支持忙闲不均、支持并发
生产者与生产者之间应保持互斥关系
消费者与消费者之间应保持互斥关系
生产者与消费者之间应保持同步+互斥关系
//基于BlockingQueue的生产者消费者模型
class BlockQueue
{
private:
std::queue<int>_queue;
int _capacity;//用于限制队列中最大节点数量
pthread_mutex_t _mutex;
pthread_cond_t _cond_product;
pthread_cond_t _cond_consumer;
public:
BlockQueue()
~BlockQueue()
bool QueuePush(int data);
bool QueuePop(int &data);
};
POSIX信号量
功能:实现线程间同步与互斥
本质:计数器(资源技术–判断当前是否能对临界资源进行操作)+等待队列+等待+唤醒
原理:
互斥原理:当只具有0/1计数时,就可实现互斥
初始计数为1,表示当前只有一个线程能够获取资源,获取资源之后计数-1,临界资源操作完毕之后计数+1,并唤醒等待队列上的线程;计数为0,其他线程进行等待
同步原理:
1.对程序逻辑进行控制(对临界资源合理操作控制)
2.通过计数判断当前是否能够对临界资源进行操作,不能操作(计数<=0),则等待
3.其他线程促使条件满足后,计数+1,唤醒等待队列上的线程
与条件变量的区别:
1.信号量不需要搭配互斥锁使用
2.信号量本身的计数就是对临界资源进行判断,条件变量需要外部用户判断
sem_t sem; //定义信号量
int sem_init(sem_t *sem, int pshared, unsigned int value); //信号量初始化
int sem_wait(sem_t *sem); //判断计数是否可以对临界资源进行操作
int sem_post(sem_t *sem); //唤醒等待队列上的线程
int sem_destroy(sem_t *sem); //销毁
使用信号量实现生产者与消费者模型
//基于唤醒队列实现
class RingQueue
{
private:
std::vector<int>_queue;
int _capacity;
int _read;
int _write;
sem_t _lock;
sem_t _idle_space;//生产者入队数据之前判断队列中是否有空闲空间,判断能否入队数据
sem_t _data_space;//消费者获取数据之前判断有数据的空间有多少,判断能否获取数据
public:
QueuePush(int data);
QueuePop(int &data);
};
四.线程池
至少一个线程+任务队列(用于并发处理任务请求)
在程序初始化时,创建固定数量的线程(最大数量限制),从任务队列中获取任务,进行处理
作用:
1.避免为大量请求创建线程,导致瞬间资源耗尽,程序崩溃的问题
2.避免大量线程频繁创建销毁所带来的时间成本
实现:线程创建+线程安全的任务队列
功能:
1.线程池退出(线程池销毁条件是在所有线程退出之后进行销毁)
2.任务的安全入队
3.任务处理