为什么 pthread 自旋锁比互斥体快得多?

问题描述 投票:0回答:1

我正在使用以下来破解多线程程序的fifo 例子 作为指导。一个线程写入队列,另一个线程读取 它。同步 fifo 访问的开销是巨大的;添加 从队列中删除 1000 万个项目大约需要三秒钟 在我的机器上,如果 pthread 互斥体用于同步,并且 1.5 如果使用 pthread 自旋锁,则为秒。

这是我用于阻止添加和阻止删除的函数 显示同步的队列:

static void
spin_while_full(volatile synced_queue *me) {
    while (me->queue->n_elements == me->queue->capacity) {
    }
}

void
synced_queue_add(synced_queue *me, void *value) {
    if (me->use_spinlocks) {
        spin_while_full(me);
        pthread_spin_lock(&me->lock);
    } else {
        pthread_mutex_lock(&me->mutex);
        while (me->queue->n_elements == me->queue->capacity) {
            pthread_cond_wait(&me->var_prod, &me->mutex);
        }
    }
    queue_add(me->queue, value);
    if (me->use_spinlocks) {
        pthread_spin_unlock(&me->lock);
    } else {
        pthread_mutex_unlock(&me->mutex);
        pthread_cond_signal(&me->var_cons);
    }
}

static void
spin_while_empty(volatile synced_queue *me) {
    while (me->queue->n_elements == 0) {
    }
}

void
synced_queue_remove(synced_queue *me, void *value) {
    if (me->use_spinlocks) {
        spin_while_empty(me);
        pthread_spin_lock(&me->lock);
    } else {
        pthread_mutex_lock(&me->mutex);
        while (me->queue->n_elements == 0) {
            pthread_cond_wait(&me->var_cons, &me->mutex);
        }
    }
    queue_remove(me->queue, value);
    if (me->use_spinlocks) {
        pthread_spin_unlock(&me->lock);
    } else {
        pthread_mutex_unlock(&me->mutex);
        pthread_cond_signal(&me->var_prod);
    }
}

me->use_spinlocks
是一个布尔标志,以便于比较 同步方法。 p 线程 手册 非常不鼓励使用自旋锁:

自旋锁应与实时结合使用 调度策略(SCHED_FIFO,或者可能是 SCHED_RR)。 使用 具有不确定性调度策略的自旋锁,例如 SCHED_OTHER 可能表示设计错误。 ...用户空间 自旋锁不适合作为通用锁定解决方案。他们 根据定义,容易发生优先级反转和无界旋转 次。使用自旋锁的程序员必须格外小心 不仅在代码方面,而且在系统配置方面, 线程放置和优先级分配。

我的问题是为什么使用互斥体的锁定比使用互斥锁慢得多 自旋锁?自旋锁的问题当然是它对 中央处理器。那么有没有一种方法可以避免使用自旋锁,但仍然可以得到良好的结果 性能?

c queue pthreads mutex spinlock
1个回答
0
投票

我会给你我有根据的猜测(这是一个需要测试来证明真/假的猜测,我通常会写成评论,但有点太长了)。

首先让我强调一下自旋锁和互斥锁之间的基本实现差异:

  1. Pthread spinlocks 几乎完全在用户空间中实现(尽管在高争用的情况下,它们可能会通过系统调用回落到实际休眠状态,具体取决于实现)。顾名思义,它们使当前 CPU 在等待获取锁时“旋转”。这通常通过原子比较和交换 CPU 指令来实现。
  2. Pthred mutexes 是通过
    futex(2)
    系统调用(至少在 Linux 上)或无论如何通过一组系统调用来实现的。与自旋锁相反,它们通过使当前线程睡眠至少直到准备好获取锁来防止等待获取操作时的 CPU 使用。

您的实现在自旋锁的情况下使用繁忙循环,而在互斥锁的情况下使用 pthread 条件。同样,第一种情况可能是仅限用户空间的作业,第二种情况意味着通过系统调用休眠/唤醒线程。这进一步使您正在测试的两种情况两极分化:在第一种情况(自旋锁)中,您将有两个线程几乎完全占用 CPU 供其独占使用,而在第二种情况(互斥锁)中,您将有两个线程“智能地“睡眠/唤醒,尝试在不需要时最小化 CPU 使用率。

就其本身而言,这已经在两种实现之间引入了巨大的开销差异:第一个实现的性能或多或少仅受 CPU 速度的限制,而第二个实现也必须更多地依赖于 CPU 的速度。用于任务调度的底层内核。执行系统调用(互斥锁)比几百条 CMPXCHG 指令(自旋锁)要昂贵得多。

最重要的是,现代 CPU 和操作系统使用动态 CPU 频率缩放,这本质上意味着每个核心的 CPU 时钟速度不是固定的,而是根据其利用率不断调整(独立于其他核心)内核、硬件本身或两者的组合(例如参见Intel P-state)。

因此,可能发生的情况是,第二个实现中由互斥锁引起的间歇性睡眠会导致 CPU 像过山车一样不断改变时钟速度,从而进一步降低性能。我在我的另一个答案中更详细地讨论了这种现象(特别关注Linux)。

© www.soinside.com 2019 - 2024. All rights reserved.