同步与互斥
-
同步概念
程序按一定顺序访问临界资源,如:生产者消费者模型…(先生产再消费) -
互斥概念
任何时刻,都只能有一个执行流访问临界资源,如:消费者与消费者…(不能同时消费同一个物品) -
引入:
在进程中,多个线程之间共享进程资源,线程与线程之间便都具有操作该进程资源(临界资源)的能力和权限,为了保护该进程资源(临界资源),便引入了线程的同步与互斥。
互斥量(mutex)
-
在进程当中,有很多变量需要各线程共享访问(共享变量),通过共享变量来实现线程间的通信,但是,线程并发的操作该变量会导致出现某些问题;
-
例如:有100个食物等待被买走,每个线程每次买走一个物品(买食物前,考虑1秒),总共有六个线程,当不对线程做任何格外的操作时,对于食物而言,每次都会有六个线程同时去买(即:六个线程并发执行),即每次食物减少6个,当食物只剩下4个时,对于六个线程而言,都可以购买食物,但是购买完之后,会发现有两个线程购买了食物0和食物-1;这也就导致了该购买过程是有问题的。
-
测试代码:https://gitee.com/free_xin/codes/j307ltvnbz9rxpi58ohde15
-
测试结果:
-
该问题的本质就是:在同一时刻中,多个线程同时访问了一个临界资源,最后导致该临界资源出现问题;也就是说,在多线程访问临界资源时,必须使这些线程之间互斥。
-
互斥量的概念
为了实现线程间的互斥,使得
1.当有线程在临界区执行时,不允许其他线程进入临界区
2.当多个线程同时访问临界区,并且临界区没有其他线程执行时,只能使得一个线程进入临界区,其他线程等待或者退出。
3.当某个线程不在临界区执行时,这个线程不能阻止其他线程进入该临界区
其本质就是一把锁,这把锁就叫互斥量。 -
操作:在要进入临界区之前加锁,在临界区执行结束之后解锁
-
互斥量的接口函数
1.初始化
a).静态分配
pthread_mutex_t mutex = PTHREA_MUTEX_INITIALIZER;(设置全局或静态变量,用宏对互斥量进行初始化)
该互斥量不用自己销毁,系统会维护该互斥量
b).动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restict attr);
mutex:要初始化的互斥量
attr:初始化属性,默认为NULL
2.销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
mutex:要销毁的互斥量
注:已经加锁的互斥量不能销毁;已经销毁的互斥量不要再对其进行加锁;静态分配的互斥量不需要销毁。
3.加锁与解锁
int pthread_mutex_lock(pthread_mutex_t *mutex); //加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex); //解锁
成功返回0,失败返回错误号
注:对与调用未解锁的互斥量的线程,若线程对其进行加锁,那么该线程会阻塞,直到该互斥量被解锁
注:因为互斥量本身也是临界资源,所以为了保护自身,加锁操作均是原子操作,解锁操作不是原子操作
加锁之后的测试代码:https://gitee.com/free_xin/codes/j307ltvnbz9rxpi58ohde15
加锁之后的测试结果:
条件变量
- 在了解条件变量之前,先了解一下生产者消费者模型
生产者消费者模型
1.一个交易环境(一个进程)
2.两个对象(生产者(可多个)(给物品进行+1操作),消费者(可多个)(给物品进行-1操作))
3.三种关系
a).生产者与生产者:互斥关系
生产者之间以互斥的方式访问物品资源,否则会造成物品超过上限的情况
b).生产者与消费者:同步且互斥关系
同步:在物品数量为0的情况下,必须要先进行生产操作,再进行消费操作。
互斥:在消费和生产同时访问到物品资源时,会导致资源被错误修改的情况
例如:在物品资源为50时,消费和生产同时访问物品资源,同时将50这个数据加载到他们各自的寄存器当中,各自修改之后,若消费者先将数据放回内存,那么生产者返回之后的数据是51,若生产者先将数据放回内存,那么消费者返回之后的数据是49,然而实际的数据应该是50,也就造成了数据的错误修改。
如图:
c).消费者与消费者:互斥关系
生产者之间以互斥的方式访问物品资源,否则会造成物品低于下限的情况
-
条件变量概念
实现线程同步的方式,在条件不满足的时候阻塞等待,在条件满足之后,采用不同的两种方式通知其等待队列当中的线程去获取互斥量,然后进行对临界资源的操作。 -
条件变量接口函数
1.初始化
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
cond:要初始化的条件变量
attr:初始化属性,默认NULL
2.销毁
int pthread_cond_destroy(pthread_cond_t *cond);
3.等待(阻塞,条件满足之后执行该接口之后的临界区)
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
cond:要等待的条件变量
mutex:要获取以及释放的互斥量
等待实质(如图):
4.唤醒等待
a).惊群唤醒(一次性直接将等待队列上的所有的线程唤醒)
int pthread_cond_broadcast(pthread_cond_t *cond);
b).一个个唤醒
int pthread_cond_signal(pthread_cond_t *cond);
注:条件变量的使用必须与互斥量搭配使用
测试代码(模拟实现生产者消费者模型):https://gitee.com/free_xin/codes/0t7k9l5vbwnidsx3or4qm96
POSIX信号量
-
本质上是一个用来描述临界资源的计数器,也是一个临界资源,为了保护自身,其P、V操作都具有原子性
和进程间通信的(SystemV)信号量相同,都是用于同步操作,这里的信号量用于线程同步。 -
关于信号量的其他特征,可以参考SystemV信号量:https://blog.csdn.net/Code_ZX/article/details/85008036
-
实现同步的方式
在生产者消费者模型中,设置两个信号量,分别为货品数量,空位数量,生产者负责对空位数信号量-1,对货品数信号量+1,消费者负责对货品数信号量-1,对空位数信号量+1;当货品数信号量为0时,阻塞,此时执行生产者,当执行生产者的线程执行完毕或者货品数量达到上限,唤醒消费者…(一直循环)
图解: -
信号量接口函数(包含头文件:<semaphore.h>)
1.初始化
int sem_init(sem_t sem, int pshared, unsigend int value);
sem:要初始化的信号量
pshared:0表示该信号量在线程间共享,非0表示该信号量在进程间共享
value:信号量初值
2.销毁
int sem_destroy(sem_t *sem);
3.等待(对信号量进行-1操作,P操作)
int sem_wait(sem_t *sem);
4.发布(对信号量进行+1操作,V操作)
int sem_post(sem_t *sem);
测试代码:https://gitee.com/free_xin/codes/zp2sxike01h5locvmbwt636
读写锁
-
读者写者模型:写者修改共享资源,读者不对共享资源进行修改,读者的数量远大于写者的数量,在读者写者模型当中,一般就采用读写锁。
概念
其本质上是一种自旋锁(等待时间短),其中写者独占,读者共享,读者和写者同时访问时,写者优先 -
接口函数
1.初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
2.销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
3.加锁
a).加读者锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
b).加写者锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
4.解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
注:适用于读者写者模型
欢迎各位莅临指导…
xiexie…