多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
# 同步,第 7 部分:读者编写器问题 > 原文:<https://github.com/angrave/SystemProgramming/wiki/Synchronization%2C-Part-7%3A-The-Reader-Writer-Problem> ## 什么是读者作家问题? 想象一下,你有一个许多线程使用的键值映射数据结构。如果没有写入数据结构,多个线程应该能够同时查找(读取)值。作者不是那么合群 - 为了避免数据损坏,一次只有一个线程可以修改(`write`)数据结构(当时没有读者可能正在阅读)。 这是 _ 读写器问题 _ 的一个例子。也就是说,我们如何有效地同步多个读者和作者,以便多个读者可以一起阅读,但作家获得独家访问? 下面显示了不正确的尝试(“lock”是`pthread_mutex_lock`的简写): ## 尝试#1 | ``` read() { lock(&m) // do read stuff unlock(&m) } ``` | ``` write() { lock(&m) // do write stuff unlock(&m) } ``` | 至少我们的第一次尝试不会遭受数据损坏(读者必须在作家写作时等待,反之亦然)!但读者也必须等待其他读者。那么让我们尝试另一种实现.. ## 尝试#2: | ``` read() { while(writing) {/*spin*/} reading = 1 // do read stuff reading = 0 } ``` | ``` write() { while(reading &#124;&#124; writing) {/*spin*/} writing = 1 // do write stuff writing = 0 } ``` | 我们的第二次尝试遭遇竞争条件 - 想象两个线程是否同时调用`read`和`write`(或两者都称为写入)。两个线程都可以继续!其次,我们可以有多个读者和多个作者,所以让我们跟踪读者或作者的总数。这让我们尝试#3, ## 尝试#3 请记住`pthread_cond_wait`执行 _ 三个 _ 动作。首先,它以原子方式解锁互斥锁然后休眠(直到它被`pthread_cond_signal`或`pthread_cond_broadcast`唤醒)。第三,唤醒线程必须在返回之前重新获取互斥锁。因此,实际上只有一个线程可以在 lock 和 unlock()方法定义的临界区内运行。 下面的实现#3 确保如果有任何作者写作,读者将进入 cond_wait。 ```c read() { lock(&m) while (writing) cond_wait(&cv, &m) reading++; /* Read here! */ reading-- cond_signal(&cv) unlock(&m) } ``` 然而,只有一个读者一次可以阅读,因为候选人#3 没有解锁互斥锁。更好的版本在阅读之前解锁: ```c read() { lock(&m); while (writing) cond_wait(&cv, &m) reading++; unlock(&m) /* Read here! */ lock(&m) reading-- cond_signal(&cv) unlock(&m) } ``` 这是否意味着作者和阅读可以同时读写?没有!首先,请记住 cond_wait 要求线程在返回之前重新获取互斥锁。因此,一次只有一个线程可以在临界区内执行代码(用**标记)! ```c read() { lock(&m); ** while (writing) ** cond_wait(&cv, &m) ** reading++; unlock(&m) /* Read here! */ lock(&m) ** reading-- ** cond_signal(&cv) unlock(&m) } ``` 作家必须等待每个人。锁定可确保相互排斥。 ```c write() { lock(&m); ** while (reading || writing) ** cond_wait(&cv, &m); ** writing++; ** ** /* Write here! */ ** writing--; ** cond_signal(&cv); unlock(&m); } ``` 上面的候选人#3 也使用`pthread_cond_signal`;这只会唤醒一个线程。例如,如果许多读者等待作者完成,那么只有一个睡觉的读者会从他们的睡眠中醒来。读写器应该使用`cond_broadcast`,以便唤醒所有线程并检查它们的 while 循环条件。 ## 饥饿的作家 上面的候选人#3 患有饥饿。如果读者经常到达,那么作家永远无法继续(“阅读”计数永远不会减少到零)。这被称为 _ 饥饿 _,并将在重负荷下被发现。我们的解决方法是为作者实现有限等待。如果作家到了,他们仍然需要等待现有的读者,但是未来的读者必须被置于“握笔”中并等待作者完成。 “握笔”可以使用变量和条件变量来实现(这样我们就可以在编写完成后唤醒线程)。 我们的计划是,当作家到来时,在等待当前读者完成之前,记录我们的写作意图(通过递增计数器“作者”)。草绘如下 - ```c write() { lock() writer++ while (reading || writing) cond_wait unlock() ... } ``` 当作家非零时,传入的读者将不被允许继续。注意'作家'表示作家已到达,而'阅读'和'写'计数器表示有 _ 有效 _ 读者或作者。 ```c read() { lock() // readers that arrive *after* the writer arrived will have to wait here! while(writer) cond_wait(&cv,&m) // readers that arrive while there is an active writer // will also wait. while (writing) cond_wait(&cv,&m) reading++ unlock ... } ``` ## 尝试#4 以下是我们对 Reader-Writer 问题的第一个解决方案。请注意,如果您继续阅读“Reader Writer 问题”,那么您会发现我们通过给予编写者优先访问锁来解决“第二个 Reader Writer 问题”。该解决方案不是最佳的。然而,它满足了我们原来的问题(N 个活跃的读者,单个活跃的作家,如果有一个持续的读者流,避免作家的饥饿)。 你能找出任何改进吗?例如,你如何改进代码,以便我们只唤醒读者或一个作家? ```c int writers; // Number writer threads that want to enter the critical section (some or all of these may be blocked) int writing; // Number of threads that are actually writing inside the C.S. (can only be zero or one) int reading; // Number of threads that are actually reading inside the C.S. // if writing !=0 then reading must be zero (and vice versa) reader() { lock(&m) while (writers) cond_wait(&turn, &m) // No need to wait while(writing here) because we can only exit the above loop // when writing is zero reading++ unlock(&m) // perform reading here lock(&m) reading-- cond_broadcast(&turn) unlock(&m) } writer() { lock(&m) writers++ while (reading || writing) cond_wait(&turn, &m) writing++ unlock(&m) // perform writing here lock(&m) writing-- writers-- cond_broadcast(&turn) unlock(&m) } ```