线程同步经典问题之读者-作者问题
问题描述:
假设一个数据库为多个并发线程所共享。有的线程只读,有的线程需要写数据库。因此要求:①允许多个读者可以同时对文件执行读操作;②只允许一个写者往文件中写信息;③任一写者在完成写操作之前不允许其他读者或写者工作;④写者执行写操作前,应让已有的读者和写者全部退出。
这个问题有两个变种:
读者优先(第一读者-作者): 要求读者不应该保持等待,除非作者已经获得了权限使用对象。
写者优先(第二读者-作者): 要求一旦作者就绪,就应该尽快的执行,其他读者应该进行等待。
两种问题都会带来了饥饿。
第一种写者饥饿;第二种读者饥饿。
读者优先:
-
关系分析。
由题目分析读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。 -
整理思路。
两个进程,即读者和写者。写者是比较简单的,它和任何进程互斥,它写入的数据部分应该属于临界区。用互斥信号量的P操作、V操作即可解决。读者的问题比较复杂,它必须实现与写者互斥的同时还要实现与其他读者的同步,因此,仅仅简单的一对P操作、V操作是无法解决的。
那么,在这里用到了一个计数器,用它来判断当前是否有读者读文件。当有读者的时候写者是无法写文件的,此时读者会一直占用文件,当没有读者的时候写者才可以写文件。同时这里不同读者对计数器的访问也应该是互斥的。 -
信号量设置。
首先设置信号量count为计数器,用来记录当前读者数量,初值为0;
设置mutex为互斥信号量,用于保护更新count变量时的互斥;
设置互斥信号量rw用于保证读者和写者的互斥访问
这部分的讲解参考:(https://blog.csdn.net/u014253011/article/details/82744241
)
int count=0; //用于记录当前的读者数量
semaphore mutex=1; //用于保护更新count变量时的互斥
semaphore rw=1; //用于保证读者和写者互斥地访问文件
writer () { //写者进程
while (1){
P(rw); // 互斥访问共享文件
Writing; //写入
V(rw) ; //释放共享文件
}
}
读者:作为读者,当你去拿一个资源时,你首先要看read_count是多少,如果是0,表示这个资源有可能被写进程占用,先用写锁将其锁住,避免写进程修改进来。当读完了,需要看,自己自己读完以后,是否是count 是0, 因为前面对 写进程进行了加锁,如果是0,需要释放让写进程可以拿到这个数据,
reader () { // 读者进程
while(1){
P (mutex) ; //互斥访问count变量
if (count==0) //当第一个读进程读共享文件时
P(rw); //阻止写进程写
count++; //读者计数器加1
V (mutex) ; //释放互斥变量count
reading; //读取
P (mutex) ; //互斥访问count变量
count--; //读者计数器减1
if (count==0) //当最后一个读进程读完共享文件
V(rw) ; //允许写进程写
V (mutex) ; //释放互斥变量 count
}
}
写者优先
在读者在运行时,有写请求进来,应该禁止后面的所有的读请求,而写请求进行到排队中,直到写请求执行完了,再次执行下一个读请求。
为此,增加一个信号量并且在上面的程序中 writer()和reader()函数中各增加一对PV操作,就可以得到写进程优先的解决程序。
int count = 0; //用于记录当前的读者数量
semaphore mutex = 1; //用于保护更新count变量时的互斥
semaphore rw=1; //用于保证读者和写者互斥地访问文件
semaphore w=1; //用于实现“写优先”
writer(){
while(1){
P(w); //在无写进程请求时进入
P(rw); //互斥访问共享文件
writing; //写入
V(rw); // 释放共享文件
V(w) ; //恢复对共享支件的访问
}
}
reader () { //读者进程
while (1){
P (w) ; // 在无写进程请求时进入
P (mutex); // 互斥访问count变量
if (count==0) //当第一个读进程读共享文件时
P(rw); //阻止写进程写
count++; //读者计数器加1
V (mutex) ; //释放互斥变量count
V(w); //恢复对共享文件的访问
reading; //读取
P (mutex) ; //互斥访问count变量
count--; //读者计数器减1
if (count==0) //当最后一个读进程读完共享文件
V(rw); //允许写进程写
V (mutex); //释放互斥变量count
}
}
下面用两种方法解决下面的读写问题:
信号量解决
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
int data = 1;
int count = 0;
sem_t rw_lock;
// 读者优先的情况下只需要两个信号量,但是会产生写线程饥饿。
// 写者优先需要三个信号量
// sem_t r_lock;
sem_t mutex;
// 读者优先
void* reader(void* arg) {
while (true) {
// 写者优先时需要打开
// sem_wait(&r_lock);
sem_wait(&mutex);
if (count == 0) {
sem_wait(&rw_lock);
}
count++;
printf("Reader has %d readers\n", count);
sem_post(&mutex);
printf("Reader data:%d\n", data);
sleep(1);
sem_wait(&mutex);
count--;
if (count == 0) {
sem_post(&rw_lock);
}
sem_post(&mutex);
sleep(5);
// 写者优先时,需要打开
// sem_post(&r_lock);
}
}
void* writer(void* arg) {
while (true) {
sem_wait(&rw_lock);
int tmp = data;
data = rand()% 100;
printf("writer modify data from %d to %d.\n", tmp, data);
sem_post(&rw_lock);
sleep(1);
}
}
int main(int argc, char* argv[]) {
sem_init(&rw_lock, 0, 1);
// 写者优先时需要打开
//sem_init(&r_lock, 0, 1);
sem_init(&mutex, 0, 1);
srand(time(NULL));
data = rand()% 100;
pthread_t p_reader[10];
pthread_t p_writer[3];
for (int i = 0; i < 3; ++i) {
pthread_create(&p_writer[i], NULL, writer, NULL);
}
for (int i = 0; i < 10; ++i) {
pthread_create(&p_reader[i], NULL, reader, NULL);
}
for (int i = 0; i < 3; ++i) {
pthread_join(p_writer[i], NULL);
}
for (int i = 0; i < 10; ++i) {
pthread_join(p_reader[i], NULL);
}
sem_destroy(&mutex);
sem_destroy(&rw_lock);
// 写者优先时,打开
// sem_destroy(&r_lock);
return 0;
}
读写锁解决
LINUX 读写锁是写者优先
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<stdlib.h>
#include <unistd.h>
pthread_rwlock_t rwlock;
int good = 0;
void *writer(void *argv) {
while(true) {
// 加上写锁
pthread_rwlock_wrlock(&rwlock);
good = rand() % 1000;
printf("%u write:%d\n",(unsigned int)pthread_self() % 100,good);
pthread_rwlock_unlock(&rwlock);
sleep(rand()%2);
}
return NULL;
}
void *reader(void *argv) {
while(true) {
// 加上读锁
pthread_rwlock_rdlock(&rwlock);
printf("%u read:%d\n",(unsigned int)pthread_self() % 100,good);
pthread_rwlock_unlock(&rwlock);
sleep(rand()%2);
}
return NULL;
}
int main() {
pthread_t pt[8];
int i;
// 初始化读写锁
pthread_rwlock_init(&rwlock,NULL);
// 创建三个写线程
for(i=0;i < 3;i++) {
pthread_create(&pt[i],NULL,writer,NULL);
}
// 5个读线程
for(;i<8;i++)
pthread_create(&pt[i],NULL,reader,NULL);
for(i=0;i<8;i++)
pthread_join(pt[i],NULL);
pthread_rwlock_destroy(&rwlock);
return 0;
}