c - pthread 中的读/写锁是如何实现的?

标签 c linux pthreads

它们是如何实现的,尤其是在 pthreads 的情况下。他们在后台使用了哪些 pthread 同步 API?一些伪代码将不胜感激。

最佳答案

我有一段时间没有进行任何 pthreads 编程,但是当我这样做时,我从未使用过 POSIX 读/写锁。问题是大多数时候互斥锁就足够了:即。您的关键部分很小,而且该区域的性能并不重要,以至于双重屏障值得担心。

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

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

基本算法是计算临界区中每个线程的数量,如果线程尚未被允许访问,则将其分流到适当的队列中等待。您的大部分工作将用于在为两个队列提供服务之间实现适当的公平/偏见。

下面的类 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 实现的起点。考虑一下您的问题的性质,并用确定接下来应该唤醒哪一类线程的适当逻辑替换出列。根据应用,通常允许有限数量/期间的读者超越作者,反之亦然。

当然,我的一般偏好是完全避免读写锁;通常通过使用原子操作、互斥锁、STM、消息传递和持久数据结构的某种组合。然而,有时您真正需要的是读写锁,当您这样做时,了解它们的工作原理很有用,所以我希望这对您有所帮助。

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

我假设 dequeue 实现包含等待,所以在 append(dequeue, mutex)prepend(dequeue, mutex) 中的某处有一个 block 代码如下:

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

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

关于c - pthread 中的读/写锁是如何实现的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11032450/

相关文章:

linux - 正确使用在进程间共享的 pthread mutex

c - 如何干净地中断recv调用上阻塞的线程?

c - Mandelbrot 使用 Pthreads,单步遍历数组

c++ - 如果从 C 源而不是 C++ 源调用 pthread_exit,取消处理程序将不会运行

c - 这两个地址怎么会不同呢?

.net - 将参数从 VB .Net 传递到 C(结构)

java - 在java程序中使用android aapt

linux - ldconfig 只链接以 lib* 开头的文件?

c - 在c中生成新变量

linux - 在不更改文件扩展名的情况下对文件夹进行 gzip