You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自研std::atomic_flag自旋锁性能仅为pthread_spinlock_t一半,求优化方向

自研atomic_flag自旋锁性能低于pthread_spinlock_t的原因与优化

我原本计划用自研的基于无锁std::atomic_flag的自旋锁替换pthread_spinlock_t,但测试发现自研实现性能远低于前者——pthread_spinlock_t执行耗时约0.9秒,自研版本耗时约2.4秒。怀疑问题和内存序有关,希望有人解释实现中的缺失和优化空间。

自研实现代码

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>
#include <sys/time.h>
#include <atomic>
#include <list>

#define LOOPS 10000000

using namespace std;

list<int> the_list;

//pthread_spinlock_t spinlock;
std::atomic_flag flag = ATOMIC_FLAG_INIT;

pid_t gettid() { return syscall( __NR_gettid ); }

void *consumer(void *ptr)
{
    printf("Consumer TID %lu\n", (unsigned long)gettid());

    while (1)
    {
        //pthread_spin_lock(&spinlock);
        while (flag.test_and_set(std::memory_order_acquire));

        if (the_list.empty())
        {
            //pthread_spin_unlock(&spinlock);
            flag.clear(std::memory_order_release);
            break;
        }

        the_list.front();
        the_list.pop_front();

        //pthread_spin_unlock(&spinlock);
        flag.clear(std::memory_order_release);
    }

    return NULL;
}

int main()
{
    int i;
    pthread_t thr1, thr2;
    struct timeval tv1, tv2;

    //pthread_spin_init(&spinlock, 0);

    // Creating the list content...
    for (i = 0; i < LOOPS; i++)
        the_list.push_back(i);

    // Measuring time before starting the threads...
    gettimeofday(&tv1, NULL);

    pthread_create(&thr1, NULL, consumer, NULL);
    pthread_create(&thr2, NULL, consumer, NULL);

    pthread_join(thr1, NULL);
    pthread_join(thr2, NULL);

    // Measuring time after threads finished...
    gettimeofday(&tv2, NULL);

    if (tv1.tv_usec > tv2.tv_usec)
    {
        tv2.tv_sec--;
        tv2.tv_usec += 1000000;
    }

    printf("Result - %ld.%ld\n", tv2.tv_sec - tv1.tv_sec,
        tv2.tv_usec - tv1.tv_usec);

    //pthread_spin_destroy(&spinlock);
    return 0;
}

性能差距的核心原因与优化方向

1. 忙等待的原子操作过于频繁

你的实现直接用while (flag.test_and_set(std::memory_order_acquire));自旋,每次循环都执行原子写操作,会持续触发缓存一致性协议(如MESI)的总线流量,导致大量缓存失效、总线争用,性能暴跌。而pthread_spinlock_t会先做一段时间的被动空转(不触发总线操作),仅在多次尝试失败后才执行原子抢占,甚至会在必要时让步给内核调度。

2. 缺少架构相关的pause指令优化

在x86架构下,自旋循环中必须加入__builtin_ia32_pause()(GCC)指令:它会告诉CPU当前线程处于自旋状态,避免乱序执行带来的性能损耗,同时降低功耗,还能让硬件优先调度其他线程。pthread的自旋锁必然包含这类架构优化,而你的实现完全缺失。

3. 内存序的使用可以更精准

虽然你用的acquire/release语义是正确的,但可以拆分优化:在自旋等待阶段用弱内存序(relaxed)检查锁状态,仅当锁可能可用时,再用强内存序的test_and_set抢占锁,减少强同步操作的次数。

优化后的自旋锁实现示例

void *consumer(void *ptr)
{
    printf("Consumer TID %lu\n", (unsigned long)gettid());

    while (1)
    {
        // 先以relaxed语义自旋等待,减少强内存序操作
        while (flag.test(std::memory_order_relaxed)) {
            // x86架构下加入pause指令优化
            __builtin_ia32_pause();
        }
        // 尝试抢占锁,用acquire语义确保后续操作可见性
        if (flag.test_and_set(std::memory_order_acquire)) {
            continue;
        }

        if (the_list.empty())
        {
            flag.clear(std::memory_order_release);
            break;
        }

        the_list.front();
        the_list.pop_front();

        flag.clear(std::memory_order_release);
    }

    return NULL;
}

额外优化点

  • 锁释放时,可配合std::atomic_thread_fence(std::memory_order_release) + flag.clear(std::memory_order_relaxed),效果与直接用release语义一致,但能降低单次原子操作的开销。
  • 高竞争场景下,可加入自旋次数阈值,超过阈值后调用sched_yield()让步给其他线程,避免持续占用CPU。

内容的提问来源于stack exchange,提问作者Cabbas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 05:27:44