读者写者模型
1、读写者模型
问题描述
在一个系统中,存在多个并发的进程或线程,这些进程或线程可以分为两类:读者(Readers)和写者(Writers)。读者只对共享资源进行读取操作,而写者则对共享资源进行写入操作。该问题需要解决以下几个关键问题:
- 互斥访问:写者与写者之间、写者与读者之间必须互斥访问共享资源,以避免数据不一致的问题。
- 并发读取:多个读者可以同时访问共享资源,以提高系统的并发性能。
解决方案
1. 读者优先
读者优先策略允许只要有读者正在访问共享资源,后续的读者可以直接进入,而写者必须等待所有读者离开后才能访问。这种策略的优点是可以提高读取操作的并发性能,但可能会导致写者饥饿,即写者长时间无法获得访问权。
2. 写者优先
写者优先策略确保当有写者等待时,后续的读者必须等待写者完成操作后才能进入,以避免写者长时间等待。但这种策略可能会导致读者饥饿。
3. 公平策略
公平策略试图平衡读者和写者的访问权,避免饥饿现象的发生。例如,可以使用一个队列来记录等待的读者和写者,按照到达的顺序依次分配访问权。
2、使用PV原语解释读写者模型
1. 读者优先
实现了基本的读写互斥,但其他的条件如只要有读者正在访问共享资源,后续的读者可以直接进入没有实现,我们需要对模型进行一些修改。
这里实现了读者优先的功能,其中针对cnt自增自减操作也需要互斥。
Wmutex没必要,Mutex本身就可以实现写者互斥。
2. 写者优先
类似读者优先
3. 读写公平
这个会根据读者写者进入顺序来进行。
3、代码实现读写者模型
1. 读者优先
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int share_data = 0;
int cnt = 0;
pthread_mutex_t Mutex;
pthread_mutex_t Rmutex;
pthread_mutex_t Wmutex;
void *Reader(void *args) { //线程编号作为参数
int number = *(int *)args;
pthread_mutex_lock(&Rmutex);
if (cnt == 0) {
pthread_mutex_lock(&Mutex);
}
cnt++;
pthread_mutex_unlock(&Rmutex);
printf("读者 %d 正在阅读: %d\n", number, share_data);
pthread_mutex_lock(&Rmutex);
cnt--;
if (cnt == 0) {
pthread_mutex_unlock(&Mutex);
}
pthread_mutex_unlock(&Rmutex);
sleep(2);
return NULL;
}
void *Writer(void* args) {
int number = *(int*) args;
pthread_mutex_lock(&Mutex);
pthread_mutex_lock(&Wmutex);
share_data++;
printf("写者 %d 在写 %d\n", number, share_data);
sleep(2);
pthread_mutex_unlock(&Wmutex);
pthread_mutex_unlock(&Mutex);
return NULL;
}
int main() {
pthread_mutex_init(&Mutex, NULL);
pthread_mutex_init(&Rmutex, NULL);
pthread_mutex_init(&Wmutex, NULL);
pthread_t readers[5], writers[3];
int reader_ids[5], writer_ids[3];
for (int i = 0; i < 5; i++) {
reader_ids[i] = i;
pthread_create(&readers[i], NULL, Reader, &reader_ids[i]);
}
for (int i = 0; i < 3; i++) {
writer_ids[i] = i;
pthread_create(&writers[i], NULL, Writer, &writer_ids[i]);
}
for (int i = 0; i < 5; i++) {
pthread_join(readers[i], NULL);
}
for (int i = 0; i < 3; i++) {
pthread_join(writers[i], NULL);
}
pthread_mutex_destroy(&Mutex);
pthread_mutex_destroy(&Rmutex);
pthread_mutex_destroy(&Wmutex);
return 0;
}
值得注意的一点,线程的调度具有不确定性。即使是相同的顺序,代码运行的结果也可能不同。
2. 写者优先
语读者优先类似
3. 读写公平
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 共享资源
int shared_data = 0;
// 互斥锁
pthread_mutex_t Mutex;
pthread_mutex_t W;
pthread_mutex_t RW;
int cnt = 0;
// 读者线程函数
void* reader(void* arg) {
int id = *(int*)arg;
// 进入临界区
pthread_mutex_lock(&W);
pthread_mutex_lock(&Mutex);
if(cnt == 0) pthread_mutex_lock(&RW);
cnt++;
pthread_mutex_unlock(&Mutex);
pthread_mutex_unlock(&W);
// 读取共享资源
printf("Reader %d is reading: %d\n", id, shared_data);
sleep(1);
// 离开临界区
pthread_mutex_lock(&Mutex);
cnt--;
if (cnt == 0) pthread_mutex_unlock(&RW);
pthread_mutex_unlock(&Mutex);
return NULL;
}
// 写者线程函数
void* writer(void* arg) {
int id = *(int*)arg;
// 进入临界区
pthread_mutex_lock(&W);
pthread_mutex_lock(&RW);
// 写入共享资源
shared_data++;
printf("Writer %d is writing: %d\n", id, shared_data);
sleep(1);
pthread_mutex_unlock(&RW);
pthread_mutex_unlock(&W);
return NULL;
}
int main() {
// 初始化互斥锁
pthread_mutex_init(&Mutex, NULL);
pthread_mutex_init(&RW, NULL);
pthread_mutex_init(&W, NULL);
// 创建线程
pthread_t readers[5], writers[3];
int reader_ids[5], writer_ids[3];
for (int i = 0; i < 5; i++) {
reader_ids[i] = i;
pthread_create(&readers[i], NULL, reader, &reader_ids[i]);
}
for (int i = 0; i < 3; i++) {
writer_ids[i] = i;
pthread_create(&writers[i], NULL, writer, &writer_ids[i]);
}
// 等待线程结束
for (int i = 0; i < 5; i++) {
pthread_join(readers[i], NULL);
}
for (int i = 0; i < 3; i++) {
pthread_join(writers[i], NULL);
}
// 销毁互斥锁
pthread_mutex_destroy(&Mutex);
pthread_mutex_destroy(&RW);
pthread_mutex_destroy(&W);
return 0;
}