单线程、多线程无锁、多线程加锁,哪种效率最高?(附测试代码)

  问题描述

  如果写一段代码,让你把从 1 到 1e8(亿)的所有自然数转为字符串,你能想到的运行最快的方式是什么?这些自然数存储在队列中的。

  这里,先暂且不从算法上深究这道题,而从多线程的角度考虑,你有什么想法呢?

  题目解读

  直观上,首先想到的肯定是单线程模式,循环读队列数据(记为 q0),调用“整数转字符串函数”处理。

  进阶

  这里提供两种多线程处理的思路:

  1)多线程无锁

  将这个长度为 1 亿的队列从中间分成两段,定义为 q1 和 q2;在主线程里开辟两个新进程 t1 和 t2;线程 t1 单独处理队列 q1,线程 t2 单独处理队列 q2

  2)多线程加锁

  在主线程里开辟两个新进程 t3 和 t4;线程 t3 和线程 t4 同时去抢队列 q4(长度为 1 亿)的锁,谁抢到谁处理。

  注意:上述两种方案中,线程个数均可以扩展为多个,不限于 2 个。

  性能数据

  编译器:clang

  语言:c++

  测试结果如下:

  单线程处理时,耗时 3.40382 秒;

  多线程无锁时,耗时 7.0449 秒;

  多线程加锁时,耗时 18.0408 秒

  测试结果

  原因分析

  相比于拆分队列的无锁方式,多线程抢锁带来了不小的开销,这点很容易理解。

  而拆分队列的情况,理论上多线程应该性能比单线程好,这里的结果却反过来了。下次有时间再分析分析吧。

  另外,有谁能告诉我用 clang 编译时,为啥 Thread 的构造函数参数不能有多个?比如

  Thread t(类中成员函数名(带作用域的),类对象指针,类中成员函数入参)这种形式就报错!

  后话

  利用多线程处理队列里的数据,是大型软件系统里的一种常见的处理方式。一般情况下,我们将线程和 vCPU(虚拟核)绑核,而队列和线程的数量关系是1对1,或者1对多,前者不需要抢锁,而后者需要抢锁。

  附录:C++ 代码,供参考

  main.cpp

  #include

  #include

  #include

  #include

  #include "Task.h"

  std::queue q0;

  std::queue q1;

  std::queue q2;

  std::queue q4;

  std::mutex mtx;

  void CreatQue()

  {

  int maxNum=1e8;

  int i=0;

  while (i < maxNum) {

  q0.push(i);

  q4.push(i);

  if(i < maxNum / 2) {

  q1.push(i);

  } else {

  q2.push(i);

  }

  i++;

  }

  };

  static void DoTask()

  {

  while (!q0.empty()) {

  int curr=q0.front();

  q0.pop();

  std::string str=std::to_string(curr);

  }

  };

  static void DoTask1()

  {

  while (!q1.empty()) {

  int curr=q1.front();

  q1.pop();

  std::string str=std::to_string(curr);

  }

  };

  static void DoTask2()

  {

  while (!q2.empty()) {

  int curr=q2.front();

  q2.pop();

  std::string str=std::to_string(curr);

  }

  };

  static void DoTaskWithLock()

  {

  while (1) {

  mtx.lock();

  if (q4.empty()) {

  mtx.unlock();

  break;

  }

  int curr=q4.front();

  q4.pop();

  std::string str=std::to_string(curr);

  mtx.unlock();

  }

  };

  int main()

  {

  double st, et;

  double cost;

  CreatQue();

  std::cout << "test single thread performance : " << std::endl;

  st=(double)clock() / CLOCKS_PER_SEC;

  DoTask();

  et=(double)clock() / CLOCKS_PER_SEC;

  cost=et - st;

  std::cout << "time cost : " << cost << std::endl;

  std::queue qq;

  std::cout << "test multi thread without lock performance : " << std::endl;

  st=(double)clock() / CLOCKS_PER_SEC;

  std::thread t1(DoTask1);

  std::thread t2(DoTask2);

  t1.join();

  t2.join();

  et=(double)clock() / CLOCKS_PER_SEC;

  cost=et - st;

  std::cout << "time cost : " << cost << std::endl;

  std::cout << "test multi thread with lock performance : " << std::endl;

  st=(double)clock() / CLOCKS_PER_SEC;

  std::thread t3(DoTaskWithLock);

  std::thread t4(DoTaskWithLock);

  t3.join();

  t4.join();

  et=(double)clock() / CLOCKS_PER_SEC;

  cost=et - st;

  std::cout << "time cost : " << cost << std::endl;

  return 0;

  }

posted @ 2022-02-22 20:10  ebuybay  阅读(366)  评论(0编辑  收藏  举报