一.什么是线程安全?如何实现线程安全?
线程安全: 多个线程/进程对临界资源的访问操作是安全的
线程安全的实现:同步与互斥
互斥: 同一时间只有一个线程/进程能够访问临界资源,实现临界资源访问的安全
同步: 使多个线程/进程按照某种规则时序进行执行调度,实现对临界资源访问的合理性
互斥的实现: 互斥锁,信号量
同步的实现: 条件变量.信号量
二.互斥锁
pthread_mutex_t mutex; //定义互斥锁
pthread_mutex_init(pthread_mutex_t* mutex,pthread_mutexattr_t* attr); //互斥锁初始化
pthread_mutex_lock(pthread_mutex_t* mutex); //加锁:可以加锁则返回,不可则阻塞
pthread_mutex_trylock(pthread_mutex_t* mutex); //加锁:不能加锁则报错返回
pthread_mutex_unlock(pthread_mutex_t* mutex); //解锁
pthread_mutex_destroy(pthread_mutex_t* mutex); //销毁互斥锁释放资源
互斥锁实现互斥的原理:
互斥锁本质就是一个只有0/1的计数器,用于标记临界资源的访问状态,每个线程在访问临界资源之前,都要先访问互斥锁,判断当前是否为可访问状态.
- 如果可以访问,则将访问状态置为不可访问状态,然后去进行资源访问(这期间别人就无法访问),访问完毕后一定记得将访问状态置为可访问状态.
- 如果不可以访问,则阻塞或者报错.
互斥锁实现自身原子操作(0/1之间转换)的原理:
为了实现互斥锁,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据进行交换.由于只有一条指令,则保证了其原子性.
寄存器:寄存器是中央处理器内的组成部分.寄存器是有限存贮容量的高速存贮部件,可以用来存储指令,数据,位址.在中央处理器的控制部件中,包含的寄存器有指令寄存器和程序计数器.在中央处理器的算数及逻辑部件中,包含的寄存器有累加器.
三.条件变量
pthread_cond_t cond; //定义条件变量变量
pthread_cond_init(pthread_cond_t* cond,pthread_condattr_t* addr); //初始化条件变量
pthread_cond_wait(pthread_cond_t* cond,pthread_mutex_t* mutex); //在一个线程不满足资源访问的情况下,是线程阻塞
pthread_cond_signal(pthread_cond_t* cond); //在资源访问条件满足之后,唤醒阻塞的线程
pthread_cond_destroy(pthread_cond_t* cond); //销毁条件变量
条件变量实现同步:
条件变量提供了使线程阻塞以及唤醒阻塞的接口,但是具体什么时候阻塞线程,什么时候唤醒线程不确定.因此在使用条件变量实现同步,需要程序员自己合理的控制时序逻辑,只需要在合适的时候调用条件变量提供的阻塞/唤醒接口.
注意事项:
- 条件判断必须使用循环操作
有可能一个厨师做完饭,唤醒多个顾客.多个顾客被唤醒后,都会去加锁.假设A顾客加锁成功,吃 饭,吃完饭解锁.解锁后假设B顾客加锁成功,如果是IF语句,则B顾客在没有饭的情况下加锁,然后 成功吃饭.因此应该循环判断,加锁成功后,再次判断有没有饭,没有则重新休眠.
- 多种角色应该使用多个条件变量,让不同的角色等待在不同的条件变量队列中,便于分角色唤醒,防止唤醒角色错误的情况
四.信号量
定义信号量 sem_t
初始化信号量 int sem_init(sem_t* sem,int pshared,int val);
pshared:如果为0则表示用于线程间;非0则表示用于进程间
val:用于初始化计数器的初值
返回值:成功则返回0 失败返回-1
在临界资源访问/获取之前进行P操作
sem_wait(sem_t* sem); 阻塞操作,表示当前计数小于等于0则阻塞
sem_trywait(sem_t* sem); 非阻塞,表示当前计数小于等于0则报错返回
sem_timedwait(sem_t* sem,struct timespec* timeout); 限时阻塞操作,表示在指定时间内若无法满足条件,等待超时后则报错返回
资源生产后,进行计数+1,唤醒阻塞V操作 int sem_post(sem_t* sem);
销毁信号量 sem_destroy(sem_t* sem);
class RingQueue{
vector<int> _list;
int _capacity;
int _read_step; //标记当前读取数据位置的下标
int _write_step;//当前写入位置的下标
sem_t _sem_lock;//用于实现互斥
sem_t _sem_idle; //空闲空间计数:生产者能否入队数据的依据
sem_t _sem_data; //数据资源个数:消费者能够获取数据的依据
};
本质:一个计数器+pcb等待队列
功能:实现进程或线程间的同步与互斥
互斥实现: 通过只有1个计数,表示资源只有一个,保证同一时间只有一个线程或者进程访问
同步实现: 通过计数器对资源进行计数,因此在资源获取时可以直接通过计数得知是否获取合理,从而进行对应操作
具体操作:
P操作: 在资源访问之前判断是否合理,计数-1.不合理则阻塞,合理则返回
V操作: 在生产资源之后,进行计数+1,唤醒一个阻塞的线程或进程
信号量与条件变量的不同在哪里?
- 信号量通过自身计数实现判断,不需要搭配互斥锁
- 条件变量需要程序员自己进行条件判断,需要搭配互斥锁使用
信号量与互斥锁的不同?
信号量可以实现互斥,但是更多的是用于同步的实现.