计算机操作系统 - 进程管理 - 进阶
经典同步问题
读者-写者问题
主线见计算机操作系统 - 进程管理 · 读者-写者问题,那里用 count、count_mutex 和 data_mutex 实现的是读者优先:只要还有读者在读,后到的读者可以直接进入,写者要一直等待。
三种实现的差别可以用同一组到达顺序来比较(按主线和本节的代码逐行模拟,信号量按先进先出的顺序唤醒):
以下内容由 @Bandi Yugandhar 提供。
主线中的实现可能导致写者饥饿。下面的实现是写者优先的:写者进入队列之后,只需要等待在它之前进入的读者,不会被之后到达的读者一直阻塞。
int readcount, writecount; // 初值都为 0
semaphore rmutex, wmutex, readLock, resource; // 初值都为 1
void reader() {
// 进入
down(&readLock); // 有写者在排队时,读者在这里被挡住
down(&rmutex); // 保护 readcount
readcount++;
if (readcount == 1)
down(&resource); // 第一个读者锁住资源,防止写者进入
up(&rmutex);
up(&readLock);
// 读
// 退出
down(&rmutex);
readcount--;
if (readcount == 0)
up(&resource); // 最后一个读者释放资源
up(&rmutex);
}
void writer() {
// 进入
down(&wmutex); // 保护 writecount
writecount++;
if (writecount == 1)
down(&readLock); // 第一个写者挡住之后到达的读者
up(&wmutex);
down(&resource); // 写者之间互斥,并等待正在读的读者退出
// 写
up(&resource);
// 退出
down(&wmutex);
writecount--;
if (writecount == 0)
up(&readLock); // 最后一个写者放行读者
up(&wmutex);
}
每个读者都必须先获取 readLock,而只有第一个写者需要获取 readLock,并且直到队列中没有写者时才会释放,因此这种实现可能导致读者饥饿。
以上两种实现总有一方可能饥饿。下面的实现使用一个先进先出的 serviceQueue 让读者和写者按到达顺序排队,获取共享数据的锁总能在有限时间内完成,因此不会出现饥饿。
int readCount; // 正在读的读者数,初值为 0
semaphore resourceAccess; // 控制对资源的读写,初值为 1
semaphore readCountAccess; // 保护 readCount,初值为 1
semaphore serviceQueue; // 按到达顺序排队,初值为 1,要求按先进先出的顺序唤醒
void writer() {
down(&serviceQueue); // 排队
down(&resourceAccess); // 等待独占资源
up(&serviceQueue); // 轮到下一个
writeResource(); // 写
up(&resourceAccess);
}
void reader() {
down(&serviceQueue); // 排队
down(&readCountAccess);
if (readCount == 0)
down(&resourceAccess); // 第一个读者锁住资源,写者被挡住
readCount++;
up(&serviceQueue); // 轮到下一个
up(&readCountAccess);
readResource(); // 读
down(&readCountAccess);
readCount--;
if (readCount == 0)
up(&resourceAccess); // 最后一个读者释放资源
up(&readCountAccess);
}
参考资料
- Tanenbaum A S, Bos H. Modern Operating Systems [M]. Pearson, 2014.
- Readers–writers problem - Wikipedia