5

在多线程 Linux 应用程序中,我将互斥锁用于关键部分。除了公平问题外,这非常有效。一个线程离开临界区并立即重新进入的情况可能不会给任何其他线程机会。例如

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

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

有没有办法制作一个公平的关键部分?我正在考虑添加一个队列,以便关键部分按照它们的“到达”顺序执行。或者,如果其他线程正在等待,至少有一个计数器可以在解锁后执行 pthread_yield()。

对于这种要求,是否有推荐的做法?

4

5 回答 5

8

您可以在 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 互斥锁,从而允许其他线程加入队列。

于 2011-06-23T12:24:28.720 回答
4

即使有一个公平的临界区,代码的性能也可能很糟糕,因为如果临界区被长时间持有,线程将经常等待它。

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

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

于 2011-06-23T08:05:43.193 回答
0

如果您的主张成立(我没有时间阅读,并且看起来好像您在发布问题之前已经对此进行了研究),我建议

 sleep(0);

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

while(true)
{
    critsect.enter();
    ... do calculations ...
    ... maybe call a blocking operation so we sleep ...
    critsect.leave();
    sleep(0);
}
于 2011-06-23T06:41:30.330 回答
0

好的,这个怎么样:

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

在里面。信号量计数为 1。在尝试获取 CS 并在完成时发出信号之前,让其他线程也等待信号量。如果“计算”线程获得 sema,它可以到达 CS 并锁定它。一旦进入锁,但在长 claculate 之前,sema 会发出信号,然后另一个线程可以到达 CS 但不能进入它。当“计算”线程退出锁定时,它不能循环并重新锁定它,因为 sema. count 为零,所以另一个线程获得了锁。“计算”线程必须在 sema 上等待,直到进入的另一个线程完成其访问并发出 sema 信号。

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

Rgds,马丁

于 2011-06-24T13:19:48.893 回答
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
    }
}
于 2018-11-19T22:54:59.993 回答