文章目录
一、自旋锁
1.1 挂起等待和自旋等待
先来看两种场景:
1️⃣ 当一个线程申请锁的时候发现申请不了就去干其他的事情了,这种状态叫做挂起等待。等着锁被释放后才能被唤醒。
2️⃣ 当一个线程申请锁的时候发现申请不了就不停的询问该锁是否被释放。我们把这种状况叫做自旋等待。
而是什么决定了等待的方式?
取决于我们要等待的时长。也就是成功申请到了临界资源的线程要在临界区待多长时间。
那么如何定义时间的长短呢?
我们可以看使用场景,当有很多IO操作或者要等待某种软件条件……一般都是要挂起等待的,如果在临界区是非常简单的内存操作一般是自旋等待。
一般情况我们都会选择挂起等待,因为如果使用自旋等待出现了死锁的情况的话,所有的执行流疯狂的自旋,会迅速占满CPU资源。
而因为选择哪种等待方式是由程序员决定的,所以我们也可以把两种方式都用一下来测试效果看看谁好谁坏。
1.2 自旋锁的接口
我们前面介绍的锁pthread_mutex和信号量sem都属于挂起等待的范畴。
而自旋锁也有它自己的类型:
pthread_spinlock_t *lock
1.2.1 自旋锁的初始化与销毁
#include <pthread.h>
int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
1.2.2 自旋锁的加锁与解锁
加锁:
#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
解锁:
#include <pthread.h>
int pthread_spin_unlock(pthread_spinlock_t *lock);
可以看到跟我们之前使用的互斥锁的接口大同小异。其实用起来跟互斥锁一摸一样,在用户眼里不管是挂起等待还是自旋等待都是等待。
二、读者写者模型
2.1 场景引入
我们小时候班级都有画黑板报的经历,我们把画黑板报的人称为写者,看黑板报的人称为读者。
我们来讨论它们之间的关系:
写者与写者:
写者和写者的关系是典型的互斥关系(不考虑画在不同的区域的情况),因为可能会发生覆盖情况。
读者与写者:
当写者没写完的时候读者就有可能会去读,这样读者也不知道自己读了个什么,所以它们之间也应该有互斥关系。
还有一种情况,写者已经写完了,都没有人来看过就擦掉了,更新了板报,那会有什么意义呢?所以读者和写者之间一定有同步关系。
读者和读者:
读者和读者之间没有关系,不存在互斥或者同步。因为大家都能看黑板报。
这跟之前我们讲过的【linux】基于阻塞队列的生产者消费者模型(条件变量)的区别就是消费者会拿走数据,读者不会。
那么读者写者问题适用于什么场景呢?
一旦发布出来,很长时间都不会做修改,大部分时间都是被读取的,就例如我们写的博客。
2.2 读写锁的接口
2.2.1 读写锁的初始化与销毁
#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
2.2.2 读写锁的加锁与解锁
读者加锁:
#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
写者加锁:
#include <pthread.h>
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
读者和写者加锁是不同的接口,但是解锁是一个接口。
解锁:
#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
2.3 伪代码原理
读者有把锁和计数器:
pthread_mutex_t rdlock;
int rdcnt = 0;
写者也有自己的一把锁:
pthread_mutex_t wrlock;
当读者读取数据的时候:
lock(&rdlock);
rdcnt++;
if(rdcnt == 1)
{
// 不让写者进来
lock(&wrlock);
}
unlock(&rdlock);
// 读取数据……
lock(&rdlock);
rdcnt--;
if(rdcnt == 0)
{
// 现在可以让写者进入
unlock(&wrlock);
}
unlock(&rdlock);
写者写入的过程就简单得多:
lock(&wrlock);
// 写入
unlock(&wrlock);
在任意的一个时刻,只允许一个写者写入,但是允许多个读者读取。
2.4 读者优先与写者优先
当有多个读者的时候,就很可能让写者无法进入,造成写者的饥饿问题,而这是正常的,毕竟读者写者模型就是要大部分时间是读取状态,我们管这种状态叫做读者优先。
那么写者优先呢?
比方说现在有十个读者,已经进去了五个,此时写者就能拦住后边没有进去的五个,等待进去的线程读完就可以写了。