2012-06-14 72 views
6

它们是如何实现的,特别是在pthreads的情况下。他们在引擎盖下使用什么pthread同步API?一些伪代码将不胜感激。如何在pthread中实现读/写锁?

+7

也许你可以阅读代码? – tbert

+1

也许[this](http://www.cognitus。净/ html/howto/pthreadSemiFAQ_10.html)可以提供帮助吗? –

回答

8

我没有做过编程一会儿任何并行线程,但是当我这样做,我从来没有使用POSIX读/写锁。问题是大多数时候互斥体就足够了:即。你的关键部分很小,而且该地区的表现并不那么重要,以至于双重障碍值得担心。

在这种情况下,性能是一个问题,通常使用原子操作(通常作为一个编译器扩展)是一个更好的选择(即额外的障碍是问题,而不是临界区的大小)。

通过你消除所有这些情况的时候,你留下,你必须需要一个真正的RW锁定特定的性能/公平/ RW偏置要求的情况;那就是当你发现POSIX rw-lock的所有相关性能/公平参数都是未定义的和实现特定的。在这一点上,你通常会更好地实施你自己的,所以你可以确保满足适当的公平/ rw-bias要求。

的基本算法是保持多少每个都在临界区的计数,如果一个线程不允许访问的是,分流其关闭,以一个合适的队列中等待。你们的大部分努力都是在实施服务两队之间的适当公平性/偏见。

下面的C类并行线程般的伪代码说明了什么,我想说的。

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实现的起点。仔细考虑一下你的问题的本质,并用适当的逻辑代替出列,以确定下一步应该唤醒哪一类线程。根据应用情况,允许有限数量/期限的读者跨越作家或反过来是很常见的。

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

编辑 - 为响应(很合理)的问题,我在哪里等待上面的伪代码:

我已经假定出队实现包含的等待,让内某处append(dequeue, mutex)prepend(dequeue, mutex)有是一段代码:

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

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

+0

你能指出一个关于为什么以及如何避免RW锁的更详细的解释吗?我一直在我认为不应该的地方使用pthread RW锁,并想更多地了解您所谈论的内容。 – puffadder

+0

这不是关于避免RW锁。正如我在回答中所说的,当你需要一个RW锁时,你应该使用一个。问题在于,很少需要RW锁,也不需要偏见/公平性要求。 Posix RW锁没有任何偏见/公平保证,因此在您可能想要使用它们的时候,它们具有讽刺意味的不可移植性。鉴于实现您自己的便携式RW锁并不难,您也可以。 – Recurse

+0

你在哪里等待代码中的信号。我在几个地方看到了信号(rw-> deque),但是没有代码要等待那个信号。 – pythonic

0

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

+1

您的评论仅适用于重入rw-lock。正如我的回答所证明的,非重入锁只需要一个计数。而且,列表是实施重入的数据结构的一个很差的选择。短阵列,位图和线性哈希表的缓存意识组合可以使簿记更有效地利用空间/时间。 – Recurse

+0

通过折返你是否意味着递归?重入是比递归锁定更强的要求。 –