公平的关键部分(Linux)

时间:2011-06-23 05:30:27

标签: linux pthreads mutex critical-section

在多线程Linux应用程序中,我使用互斥锁来处理关键部分。除公平问题外,这种方法效果很好。一个线程离开临界区并立即重新进入并不会给任何其他线程带来机会。例如

while(true)
{
    critsect.enter();
    ... do calculations ...
    ... maybe call a blocking operation so we sleep ...
    critsect.leave();
}

可能很可能会阻止任何其他线程进入同一个关键部分。互斥是不公平的。

是否有制定公平关键部分的解决方案?我正在考虑添加一个队列,以便按照“到达”的顺序执行关键部分。或者,如果其他线程正在等待,至少可以在解锁后执行pthread_yield()。

是否有针对此类要求的推荐做法?

5 个答案:

答案 0 :(得分:7)

您可以在pthreads互斥锁之上构建一个FIFO“票证锁”,沿着以下几行:

#include <pthread.h>

typedef struct ticket_lock {
    pthread_cond_t cond;
    pthread_mutex_t mutex;
    unsigned long queue_head, queue_tail;
} ticket_lock_t;

#define TICKET_LOCK_INITIALIZER { PTHREAD_COND_INITIALIZER, PTHREAD_MUTEX_INITIALIZER }

void ticket_lock(ticket_lock_t *ticket)
{
    unsigned long queue_me;

    pthread_mutex_lock(&ticket->mutex);
    queue_me = ticket->queue_tail++;
    while (queue_me != ticket->queue_head)
    {
        pthread_cond_wait(&ticket->cond, &ticket->mutex);
    }
    pthread_mutex_unlock(&ticket->mutex);
}

void ticket_unlock(ticket_lock_t *ticket)
{
    pthread_mutex_lock(&ticket->mutex);
    ticket->queue_head++;
    pthread_cond_broadcast(&ticket->cond);
    pthread_mutex_unlock(&ticket->mutex);
}

在这种方案下,当线程在受到故障保护的关键部分内时,不会保留低级pthreads互斥锁,允许其他线程加入队列。

答案 1 :(得分:4)

即使有一个公平的关键部分,代码可能会有可怕的性能,因为如果关键部分被持有很长一段时间,线程将经常等待它。

所以我建议你尝试重新构建代码,这样就不需要在很长一段时间内锁定关键部分。通过完全使用不同的方法(通常建议通过消息队列传递对象,因为它很容易正确)或者至少通过对局部变量进行大部分计算而不保持锁定而不是仅使用锁来存储结果。如果锁定保持较短的时间段,则线程将花费较少的时间等待它,这通常会提高性能并使公平性成为无问题。您还可以尝试增加锁粒度(单独锁定较小的对象),这也会减少争用。

编辑:好的,考虑一下,我相信Linux中的每个关键部分都是公平的。每当有睡眠者时,解锁操作必须进入内核以告诉它唤醒它们。在从内核返回期间,调度程序运行并选择具有最高优先级的进程。在等待时,睡眠者会优先上升,所以在某些时候它们会足够高,以至于释放将导致任务开始。

答案 2 :(得分:0)

如果您的声明成立(我没有时间阅读,看起来好像您在发布问题之前已经研究了这个),我建议

 sleep(0);

在关键部分之间明确屈服。

while(true)
{
    critsect.enter();
    ... do calculations ...
    ... maybe call a blocking operation so we sleep ...
    critsect.leave();
    sleep(0);
}

答案 3 :(得分:0)

好的,这个怎么样:

while(true)
{
    sema.wait;
    critsect.enter();
    sema.post;
    ... do calculations ...
    ... maybe call a blocking operation so we sleep ...
    critsect.leave();
}

初​​始化。信号量计数为1.在尝试获取CS并在完成时发出信号之前,还有其他线程在信号量上等待。如果'calculate'线程获得sema,它可以到达CS并锁定它。一旦进入锁定状态,但在长时间计算之前,sema会发出信号,然后另一个线程可以到达CS但不会进入其中。当'calculate'线程退出锁时,它不能循环并重新锁定它,因为sema。 count为零,因此另一个线程获得锁定。 'calculate'线程必须等待sema,直到进入的另一个线程完成其访问并发出sema信号。

通过这种方式,另一个线程可以“保留”对数据的访问权限,即使它实际上还无法获得数据。

RGDS, 马丁

答案 4 :(得分:0)

恕我直言,您可以在Linux上使用FIFO调度程序并更改线程的优先级:

thread_func() {
    ... 
    pthread_t t_id = pthread_self();
    struct sched_param prio_zero, prio_one;
    prio_zero.sched_priority = sched_get_priority_min(SCHED_FIFO);
    prio_one.sched_priority = sched_get_priority_min(SCHED_FIFO) + 1;
    phtread_setschedparam(t_id, SCHED_FIFO, &prio_zero);
    ...
    while(true)
    {
        ... Doing something before
        phtread_setschedparam(t_id, SCHED_FIFO, &prio_one);
        critsect.enter();
        ... do calculations ...
        ... maybe call a blocking operation so we sleep ...
        critsect.leave();
        phtread_setschedparam(t_id, SCHED_FIFO, &prio_zero);
        ... Do something after
    }
}