如何在pthread中实现读/写锁?

时间:2012-06-14 11:49:31

标签: c linux pthreads

如何在pthreads的情况下实现它们。他们在引擎盖下使用了哪些pthread同步API?一点点伪代码将不胜感激。

2 个答案:

答案 0 :(得分:9)

我暂时没有做任何pthreads编程,但是当我这样做时,我从未使用过POSIX读/写锁。问题是大多数情况下互斥锁就足够了:即。你的关键部分很小,而且这个区域对性能不太重要,以至于双重障碍值得担心。

在性能成为问题的情况下,通常使用原子操作(通常作为编译器扩展提供)是更好的选择(即额外的障碍是问题,而不是关键部分的大小)。

当你消除所有这些情况时,你会遇到需要真正的rw-lock的特定性能/公平性/ rw-bias要求的情况;那就是当你发现POSIX rw-lock的所有相关性能/公平参数都是未定义的和特定于实现的时候。在这一点上,你通常最好不要实现自己的,这样你就可以确保满足适当的公平/ rw偏见要求。

基本算法是保持关键部分中每个部分的数量,并且如果还不允许访问线程,则将其分流到适当的队列以等待。您的大部分努力都是在为两个队列提供服务之间实现适当的公平/偏见。

以下类似C-pthreads的伪代码说明了我想说的内容。

struct rwlock {
  mutex admin; // used to serialize access to other admin fields, NOT the critical section.
  int count; // threads in critical section +ve for readers, -ve for writers.
  fifoDequeue dequeue; // acts like a cond_var with fifo behaviour and both append and prepend operations.
  void *data; // represents the data covered by the critical section.
}

void read(struct rwlock *rw, void (*readAction)(void *)) {
  lock(rw->admin);
  if (rw->count < 0) {
    append(rw->dequeue, rw->admin);
  }
  while (rw->count < 0) {
    prepend(rw->dequeue, rw->admin); // Used to avoid starvation.
  }
  rw->count++;
  // Wake the new head of the dequeue, which may be a reader.
  // If it is a writer it will put itself back on the head of the queue and wait for us to exit.
  signal(rw->dequeue); 
  unlock(rw->admin);

  readAction(rw->data);

  lock(rw->admin);
  rw->count--;
  signal(rw->dequeue); // Wake the new head of the dequeue, which is probably a writer.
  unlock(rw->admin);
}

void write(struct rwlock *rw, void *(*writeAction)(void *)) {
  lock(rw->admin);
  if (rw->count != 0) {
    append(rw->dequeue, rw->admin);
  }
  while (rw->count != 0) {
    prepend(rw->dequeue, rw->admin);
  }
  rw->count--;
  // As we only allow one writer in at a time, we don't bother signaling here.
  unlock(rw->admin);

  // NOTE: This is the critical section, but it is not covered by the mutex!
  //       The critical section is rather, covered by the rw-lock itself.
  rw->data = writeAction(rw->data);

  lock(rw->admin);
  rw->count++;
  signal(rw->dequeue);
  unlock(rw->admin);
}

上述代码之类的东西是任何rwlock实现的起点。考虑一下你的问题的本质,并用适当的逻辑替换dequeue,该逻辑确定接下来要唤醒哪个线程类。根据申请,允许有限数量/期限的读者超越作者或反之亦然。

当然,我的首选是完全避免使用rw-lock;通常使用原子操作,互斥体,STM,消息传递和持久数据结构的某种组合。然而,有时你真正需要的是一个锁定,当你这样做时,知道它们如何工作是有用的,所以我希望这有帮助。

编辑 - 回答(非常合理的)问题,我在哪里等待上面的伪代码:

我假设dequeue实现包含wait,所以在append(dequeue, mutex)prepend(dequeue, mutex)内的某处有一行代码:

while(!readyToLeaveQueue()) {
  wait(dequeue->cond_var, mutex);
}

这就是为什么我将相关的互斥锁传递给队列操作。

答案 1 :(得分:0)

每个实现可能会有所不同,但由于POSIX要求线程能够多次获取rwlock的读锁,通常它们必须默认支持读者。如果他们喜欢编写者,那么每当作者等待时,读者就会在第二次读取锁定尝试时死锁,除非实现可以确定读者已经有读锁定,但唯一的方法是确定存储所有线程的列表持有读锁,这在时间和空间要求上非常低效。