单线程、多线程无锁、多线程加锁,哪种效率最高?(附测试代码)
问题描述
如果写一段代码,让你把从 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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了