在之前的博客中我们说明了生产者消费者模型,在这篇博客中我们讨论类似的模型读者写者模型。
概念
读者写者模型是操作系统的一种同步与互斥机制,与生产者消费者模型相似,区别在于多个读者可以共享缓冲区,但是对于写者而言相互是竞争关系,一次只能有一个写者,写入缓冲区。
读者写者模型具有的条件
- 写者排他性:有多个写者的情况下,只有一个写者能够占用缓冲区。
- 读者的并行机制:可以有多个读者同时使用缓冲区。
- 如果写者占用缓冲区,则读者会被阻塞。
读者写者模型关系
-
读者优先:读者先进行读数据,写者暂时阻塞,直到缓冲区中无数据,写者访问仓库。
-
写者优先:写者先进行写数据,读者暂时阻塞,直到缓冲区中满数据,读者访问仓库。
-
公平情况:读者写者优先级相同,谁先进优先级队列,谁先运行。
读写锁
我们使用读写锁实现读者写者模型,读写锁和mutex相似,一共有三种状态,读模式下加锁,写模式下加锁,解锁。
在同一时间中,只能有一个线程申请写锁,但是有多个线程申请读锁。
读写锁状态
- 写加锁状态:当处于写加锁状态,所有申请锁的进程都会阻塞。
- 读加锁状态:处于读加锁状态,申请读模式下的锁的进程都能获得访问,但是对于申请写模式下的锁的进程受到阻塞。
当读写锁处于读模式下,有进程申请写模式的读写锁会导致,之后的申请读模式的读写锁的进程遭到阻塞,原因是防止读模式长期占用资源,导致写进程饥饿。
递归锁/非递归锁
递归锁也叫可重入锁(reentrant mutex),非递归锁也叫不可重入锁(non-reentrant mutex)。
二者唯一的区别是:
同一个线程可以多次获取同一个递归锁,不会产生死锁。
如果一个线程多次获取同一个非递归锁,则会产生死锁。
读写锁接口
设置读写优先
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr,int pref);
/*
pref 共有三种参数
PTHREAD_RWLOCK_PREFER_READER_NP 读者优先,可能会导致写者饥饿。
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但是不能递归加锁
*/
初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
加锁和解锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
相关案例:
//
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
using namespace std;
volatile int ticket = 1000;
pthread_rwlock_t rwlock;
class ThreadAttr
{
pthread_t tid;
string id;
};
void* reader(void* arg)
{
char* id = (char*)arg;
while (1)
{
pthread_rwlock_rdlock(&rwlock);
if (tiket <= 0)
{
pthread_rwlock_unlock(&rwlock);
break;
}
printf("%s:%d\n", id, ticket);
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
return nullptr;
}
void* writer(void* arg)
{
char* id = (char*)arg;
while (1)
{
pthread_rwlock_rdlock(&rwlock);
if (tiket <= 0)
{
pthread_rwlock_unlock(&rwlock);
break;
}
printf("%s:%d\n", id, --ticket);
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
return nullptr;
}
string create_reader_id(std::size_t i)
{
ostringstream oss("thread reader", ios_base::ate);
oss << i;
return oss.str();
}
string create_writer_id(std::size_t i)
{
ostringstream oss("thread writer", ios_base::ate);
oss << i;
return oss.str();
}
void init_readers(vector<ThreadAttr>& vec)
{
for (size_t i = 0; i < vec.size(); i++)
{
vec[i].id = create_read_id();
pthread_create(&vec[i].tid, nullptr, read, (void*)vec[i].id.c_str());
}
}
void init_writers(vector<ThreadAttr>& vec)
{
for (size_t i = 0; i < vec.size(); i++)
{
vec[i].id = create_writer_id();
pthread_create(&vec[i].tid, nullptr, writer, (void*)vec[i].id.c_str());
}
}
void join_thread(vector<ThreadAttr> const& vec)
{
for (vector<ThreadAttr>::const_reverse_iterator it = vec.rbegin(); it != vec.rend(); it++)
{
pthread const& tid = it->tid;
pthead_join(tid, nullptr);
}
}
void init_rwlock()
{
//写优先
pthread_rwlockattr_t attr;
pthread_rwlockattr_init(&attr);
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NP);//写者优先
pthread_rwlock_init(&rwlock, &attr);
pthread_rwlockattr_destroy(&attr);
//读优先 pthread_rwlock_init(&rwlock, nullptr);
}
int main()
{
const size_t reader_num = 1000;
const size_t writer_num = 2;
vector<ThreadAttr> reader(reader_num);
vector<ThreadAttr> writer(writer_num);
init_rwlock();
init_readers(reader);
init_readers(writer);
join_threads(reader);
join_threads(writer);
pthread_rwlock_destroy(&rwlock);
}