我目前正在学习大学的并发性。在这种情况下,我必须在C中实现读写器问题,我认为我正在走上正轨。
我对这个问题的想法是,我们需要两把锁rd_lock
和wr_lock
。当编写器线程想要更改我们的全局变量时,它会尝试获取两个锁,写入全局变量并解锁。当读者想要阅读全局时,它检查wr_lock
当前是否被锁定,然后读取该值,但是其中一个读者线程应该抓住rd_lock
,但其他读者不应该关心rd_lock
是否被锁定。
我不允许使用pthread库中已有的实现。
typedef struct counter_st {
int value;
} counter_t;
counter_t * counter;
pthread_t * threads;
int readers_tnum;
int writers_tnum;
pthread_mutex_t rd_lock;
pthread_mutex_t wr_lock;
void * reader_thread() {
while(true) {
pthread_mutex_lock(&rd_lock);
pthread_mutex_trylock(&wr_lock);
int value = counter->value;
printf("%d\n", value);
pthread_mutex_unlock(&rd_lock);
}
}
void * writer_thread() {
while(true) {
pthread_mutex_lock(&wr_lock);
pthread_mutex_lock(&rd_lock);
// TODO: increment value of counter->value here.
counter->value += 1;
pthread_mutex_unlock(&rd_lock);
pthread_mutex_unlock(&wr_lock);
}
}
int main(int argc, char **args) {
readers_tnum = atoi(args[1]);
writers_tnum = atoi(args[2]);
pthread_mutex_init(&rd_lock, 0);
pthread_mutex_init(&wr_lock, 0);
// Initialize our global variable
counter = malloc(sizeof(counter_t));
counter->value = 0;
pthread_t * threads = malloc((readers_tnum + writers_tnum) * sizeof(pthread_t));
int started_threads = 0;
// Spawn reader threads
for(int i = 0; i < readers_tnum; i++) {
int code = pthread_create(&threads[started_threads], NULL, reader_thread, NULL);
if (code != 0) {
printf("Could not spawn a thread.");
exit(-1);
} else {
started_threads++;
}
}
// Spawn writer threads
for(int i = 0; i < writers_tnum; i++) {
int code = pthread_create(&threads[started_threads], NULL, writer_thread, NULL);
if (code != 0) {
printf("Could not spawn a thread.");
exit(-1);
} else {
started_threads++;
}
}
}
目前,当使用1个阅读器和1个写入器运行时,它只会打印很多零,这意味着它从不实际执行编写器线程中的代码。我知道这不会像多个读者那样按预期工作,但是当我用其中一个读者运行它时,我不明白出了什么问题。
不要把锁视为“读卡器锁”和“作者锁”。
因为您需要允许多个并发读取器,所以读者无法持有互斥锁。 (如果它们这样做,它们被序列化;只有一个可以同时持有互斥锁。)它们可以在短时间内(在它们开始访问之前,在它们结束访问之后)获取一个,以更新状态,但那是它。
将rwlock分为三个部分的时间线:“抓住rwlock”,“do work”,“释放rwlock”。
例如,您可以使用一个互斥锁,一个条件变量和一个计数器。该计数器包含活跃读者的数量。条件变量由最后一个读取器和作者在释放互斥锁之前发出信号,以唤醒等待的写入器。互斥锁保护两者,并在写操作的整个持续时间内由写入器保持。
所以,在伪代码中,你可能有
Function rwlock_rdlock:
Take mutex
Increment counter
Release mutex
End Function
Function rwlock_rdunlock:
Take mutex
Decrement counter
If counter == 0, Then:
Signal_on cond
End If
Release mutex
End Function
Function rwlock_wrlock:
Take mutex
While counter > 0:
Wait_on cond
End Function
Function rwlock_unlock:
Signal_on cond
Release mutex
End Function
请记住,无论何时等待条件变量,互斥锁都会在等待期间原子释放,并在线程唤醒时自动获取。因此,对于等待条件变量,线程将在等待之前和之后都具有互斥锁,但在等待本身期间不会。
现在,上述方法并不是您可能实施的唯一方法。
特别是,您可能会注意到在上面的方案中,您必须使用不同的“解锁”操作,具体取决于您是否对rwlock进行了读取或写入锁定。在POSIX pthread_rwlock_t
实现中,只有一个pthread_rwlock_unlock()
。
无论你设计什么样的方案,重要的是检查它是否适用于所有情况:一个独立的读锁柜,一个独立的写锁柜,几个读锁柜,几个写锁柜,一个独立的写锁柜和一个读取锁定器,单独的写锁定器和几个读取锁定器,几个写入锁定器和一个单独的读取锁定器,以及几个读取和写入锁定器。
例如,让我们考虑有几个活动读者的情况,并且编写者想要写锁定rwlock。
作者抓住了互斥体。然后它注意到计数器非零,因此它开始等待条件变量。当最后一个读者 - 注意读者退出的顺序无关紧要,因为使用了一个简单的计数器! - 解锁rwlock上的读锁,它在条件变量上发出信号,唤醒编写器。然后作者抓住互斥锁,看到计数器为零,然后开始工作。在此期间,互斥锁由作者保存,因此所有新读者都将阻止,直到作者释放互斥锁。因为作者在释放互斥锁时也会在条件变量上发出信号,这是其他等待的作者和等待的读者之间的竞争,他们接下来会去。