多线程性能测试:ConcurrentQueue(无锁队列)、std::atomic_flag 和 std::mutex

本文通过实验对比了ConcurrentQueue、std::atomic_flag与std::mutex在多线程环境下的性能差异,特别是在不同数据大小及操作系统上的表现。结果显示,在特定条件下,ConcurrentQueue表现出最优性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 概要

本文对比了多线程环境下,ConcurrentQueue(无锁队列)、std::atomic_flagstd::mutex 三种同步机制的性能表现。测试场景包括插入(push)和删除(pop)操作,分别针对小数据项和大数据项进行评测。

测试目标:

  1. 比较 std::mutexstd::atomic_flag 的性能差异。参考文献: mutex 和 spin lock 的区别
  2. 测试多线程环境下的 concurrentqueue 队列性能。
  3. 测试单线程环境下的 readerwriterqueue 性能。
  4. 验证基于 C++ STL 利用 CAS 原子操作封装的无锁 list 的性能。参考文献: 无锁 list

结论

  • 对于小型数据结构(如 std::liststd::deque)的多线程操作,推荐使用 std::atomic_flag 替代 std::mutex,以提高性能。
  • 对于较大的数据结构,std::mutex 在保证线程安全的同时提供了更好的性能。
  • 新的业务代码可以根据需求适当使用无锁队列 ConcurrentQueue,尤其在处理大量数据时表现优秀。

完整测试代码: lock_test

2. 性能测试结果

2.1 测试一:30 线程并发,处理 1 万条 2KB 大小的数据(pushpop

测试结果
测试平台队列类型pushTime (ms)popTime (ms)
Linux-arm1ConcurrentQueue595.476328.856
atomic_flag412.675955.207
std::mutex946.301907.553
Linux-arm2ConcurrentQueue1584.1333.36
atomic_flag576.2091479.5
std::mutex1133.681107.63
Linux-arm3ConcurrentQueue1005.89244.84
atomic_flag355.606402.343
std::mutex597.448739.805
LinuxConcurrentQueue140.89980.4264
atomic_flag136.703136.91
std::mutex231.019213.732
WindowsConcurrentQueue200.119142.239
atomic_flag602.542482.394
std::mutex483.498306.393
结论:
  • Linux 平台
    • push 数据时,atomic_flag 优于 ConcurrentQueuemutex 最差。
    • pop 数据时,ConcurrentQueue 性能最佳。
  • Windows 平台
    • 在所有场景中,ConcurrentQueue 的表现最好。

2.2 测试二:30 线程并发,处理 1 万条 20KB 大小的数据(pushpop

测试结果
测试平台队列类型pushTime (ms)popTime (ms)
Linux-arm1ConcurrentQueue6936.41732.457
atomic_flag4256.777103.61
std::mutex5165.124044.51
Linux-arm2ConcurrentQueue18411.2942.713
atomic_flag5750.0711232.5
std::mutex7236.026573.35
Linux-arm3ConcurrentQueue5399.57247.965
atomic_flag2253.271285.47
std::mutex2625.551588.46
Linux-x86ConcurrentQueue2231.09183.022
atomic_flag1117.93715.601
std::mutex1288.95805.378
WindowsConcurrentQueue8047.595098.22
atomic_flag16736.226468.2
std::mutex21498.350173.6
结论:

随着数据项大小的增大,atomic_flag 的性能不再领先,反而有所下降,而 ConcurrentQueue 的性能持续优异,尤其在插入(push)操作中。

  • Linux 平台
    • push 数据时,atomic_flag 优于 std::mutexConcurrentQueue 最慢。
    • pop 数据时,ConcurrentQueue 性能最好。
  • Windows 平台
    • ConcurrentQueue 的表现始终最优。

3. 硬件配置

  • Linux-x86
CPU: Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz (8 cores)
内存: 16GB
操作系统: Linux 5.4.0-47-generic #51~18.04.1-Ubuntu SMP x86_64
  • 测试机器配置 (Linux-arm1)
CPU: 96 cores, ARM architecture
内存: 64GB
操作系统: Linux 4.15.0-71-generic #4 SMP aarch64
  • 测试机器配置 (Linux-arm2)
CPU: 96 cores, ARM architecture
内存: 256GB
操作系统: Linux 4.15.0-71-generic #2 SMP aarch64
  • 测试机器配置 (Linux-arm3)
CPU: Unknown
内存: 256GB
操作系统: Linux 4.15.0-45-generic #48~16.04.1-Ubuntu SMP x86_64
  • Windows 10 x64 测试机配置
CPU: Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz
内存: 8GB (1867 MHz)

4. 其他说明

测试中曾尝试验证基于 C++ STL 利用 CAS 原子操作封装的无锁 list 的效果,但结果不尽如人意,可能与新版 GCC 优化有关。因此不推荐使用此方案。

5. 参考文献

6. 完整代码

#include <iostream>
#include <list>
#include <deque>
#include <vector>
#include <thread>
#include <atomic>
#include <mutex>
#include <chrono>
#include "concurrentqueue.h"  // Include ConcurrentQueue library

// 定义小数据结构
struct SmallItem {
  int data[20];
};

// 定义大数据结构
struct LargeItem {
  int data[1024];
};

// 测试参数
const int NUM_THREADS = 30;
const int NUM_ITEMS = 10000;

// 测试函数模板
template<typename T>
void testPerformance(std::atomic_flag& lock, std::list<T>& container, std::vector<T>& data) {
  auto start = std::chrono::high_resolution_clock::now();

  std::vector<std::thread> threads;
  for (int i = 0; i < NUM_THREADS; ++i) {
    threads.push_back(std::thread([&]() {
      for (const auto& item : data) {
        while (lock.test_and_set(std::memory_order_acquire));
        container.push_back(item);
        lock.clear(std::memory_order_release);
      }
      }));
  }

  for (auto& t : threads) {
    t.join();
  }

  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double> elapsed = end - start;
  std::cout << "Push time: " << elapsed.count() << " seconds\n";

  start = std::chrono::high_resolution_clock::now();
  threads.clear();
  for (int i = 0; i < NUM_THREADS; ++i) {
    threads.push_back(std::thread([&]() {
      for (int j = 0; j < NUM_ITEMS / NUM_THREADS; ++j) {
        while (lock.test_and_set(std::memory_order_acquire));
        if (!container.empty()) {
          container.pop_front();
        }
        lock.clear(std::memory_order_release);
      }
      }));
  }

  for (auto& t : threads) {
    t.join();
  }

  end = std::chrono::high_resolution_clock::now();
  elapsed = end - start;
  std::cout << "Pop time: " << elapsed.count() << " seconds\n";
}

template<typename T>
void testPerformance(std::mutex& mtx, std::list<T>& container, std::vector<T>& data) {
  auto start = std::chrono::high_resolution_clock::now();

  std::vector<std::thread> threads;
  for (int i = 0; i < NUM_THREADS; ++i) {
    threads.push_back(std::thread([&]() {
      for (const auto& item : data) {
        std::lock_guard<std::mutex> lock(mtx);
        container.push_back(item);
      }
      }));
  }

  for (auto& t : threads) {
    t.join();
  }

  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double> elapsed = end - start;
  std::cout << "Push time: " << elapsed.count() << " seconds\n";

  start = std::chrono::high_resolution_clock::now();
  threads.clear();
  for (int i = 0; i < NUM_THREADS; ++i) {
    threads.push_back(std::thread([&]() {
      for (int j = 0; j < NUM_ITEMS / NUM_THREADS; ++j) {
        std::lock_guard<std::mutex> lock(mtx);
        if (!container.empty()) {
          container.pop_front();
        }
      }
      }));
  }

  for (auto& t : threads) {
    t.join();
  }

  end = std::chrono::high_resolution_clock::now();
  elapsed = end - start;
  std::cout << "Pop time: " << elapsed.count() << " seconds\n";
}

template<typename T>
void testPerformance(moodycamel::ConcurrentQueue<T>& queue, std::vector<T>& data) {
  auto start = std::chrono::high_resolution_clock::now();

  std::vector<std::thread> threads;
  for (int i = 0; i < NUM_THREADS; ++i) {
    threads.push_back(std::thread([&]() {
      for (const auto& item : data) {
        queue.enqueue(item);
      }
      }));
  }

  for (auto& t : threads) {
    t.join();
  }

  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double> elapsed = end - start;
  std::cout << "Push time: " << elapsed.count() << " seconds\n";

  start = std::chrono::high_resolution_clock::now();
  threads.clear();
  for (int i = 0; i < NUM_THREADS; ++i) {
    threads.push_back(std::thread([&]() {
      T item;
      for (int j = 0; j < NUM_ITEMS / NUM_THREADS; ++j) {
        queue.try_dequeue(item);
      }
      }));
  }

  for (auto& t : threads) {
    t.join();
  }

  end = std::chrono::high_resolution_clock::now();
  elapsed = end - start;
  std::cout << "Pop time: " << elapsed.count() << " seconds\n";
}

int main() {
  std::vector<SmallItem> smallData(NUM_ITEMS);
  std::vector<LargeItem> largeData(NUM_ITEMS);

  std::cout << "Testing with std::atomic_flag and SmallItem\n";
  std::list<SmallItem> smallList;
  std::atomic_flag atomicFlag = ATOMIC_FLAG_INIT;
  testPerformance(atomicFlag, smallList, smallData);

  std::cout << "Testing with std::mutex and SmallItem\n";
  std::list<SmallItem> smallListMutex;
  std::mutex mtx;
  testPerformance(mtx, smallListMutex, smallData);

  std::cout << "Testing with ConcurrentQueue and SmallItem\n";
  moodycamel::ConcurrentQueue<SmallItem> smallQueue;
  testPerformance(smallQueue, smallData);

  std::cout << "Testing with std::atomic_flag and LargeItem\n";
  std::list<LargeItem> largeList;
  testPerformance(atomicFlag, largeList, largeData);

  std::cout << "Testing with std::mutex and LargeItem\n";
  std::list<LargeItem> largeListMutex;
  testPerformance(mtx, largeListMutex, largeData);

  std::cout << "Testing with ConcurrentQueue and LargeItem\n";
  moodycamel::ConcurrentQueue<LargeItem> largeQueue;
  testPerformance(largeQueue, largeData);

  return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值