自研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
相关产品推荐
相关产品推荐

