在C++项目中使用多线程提升性能时,不少开发者会遇到程序运行效率不升反降的情况,这通常是因为没有做好多线程的性能优化。本文将从多个实用角度讲解C++多线程性能优化的具体方法,帮助开发者解决常见的多线程性能问题。

减少锁竞争开销
锁竞争是多线程性能下降的最常见原因,多个线程频繁争抢同一把锁会导致大量线程阻塞等待,浪费CPU资源。优化锁竞争可以从以下几个方面入手:
缩小锁的持有范围
只把需要同步的临界区代码放在锁内部,尽量减少锁的持有时间,避免把无关的计算、IO操作放在锁范围内。
#include <mutex>
#include <vector>
std::mutex g_mutex;
std::vector<int> g_data;
// 优化前:锁持有范围过大
void bad_add_data(int val) {
std::lock_guard<std::mutex> lock(g_mutex);
// 模拟无关计算,浪费锁持有时间
int temp = val * 2;
g_data.push_back(temp);
}
// 优化后:缩小锁持有范围
void good_add_data(int val) {
// 先完成无关计算
int temp = val * 2;
std::lock_guard<std::mutex> lock(g_mutex);
g_data.push_back(temp);
}使用合适的锁类型
根据实际场景选择锁类型,避免盲目使用互斥锁。如果是读多写少的场景,可以使用读写锁std::shared_mutex,允许多个读线程同时访问,只有写线程需要独占锁,减少竞争。
#include <shared_mutex>
#include <unordered_map>
#include <string>
std::shared_mutex g_rw_mutex;
std::unordered_map<std::string, int> g_config;
// 读操作使用共享锁
int get_config(const std::string& key) {
std::shared_lock<std::shared_mutex> lock(g_rw_mutex);
auto it = g_config.find(key);
if (it != g_config.end()) {
return it->second;
}
return -1;
}
// 写操作使用独占锁
void set_config(const std::string& key, int val) {
std::unique_lock<std::shared_mutex> lock(g_rw_mutex);
g_config[key] = val;
}避免不必要的线程同步
不是所有多线程场景都需要严格同步,过度同步会增加额外开销。可以通过线程本地存储、数据分区等方式减少同步需求。
使用线程本地存储
如果某些数据是每个线程独立使用的,不需要共享,可以使用thread_local关键字声明线程本地变量,每个线程拥有独立的副本,不需要加锁同步。
#include <thread>
#include <iostream>
thread_local int g_thread_counter = 0;
void thread_func(int thread_id) {
for (int i = 0; i < 5; i++) {
g_thread_counter++;
std::cout << "Thread " << thread_id << " counter: " << g_thread_counter << std::endl;
}
}
int main() {
std::thread t1(thread_func, 1);
std::thread t2(thread_func, 2);
t1.join();
t2.join();
return 0;
}数据分区减少共享
将共享数据按照线程拆分,每个线程处理自己分区内的数据,最后再合并结果,减少线程间的共享和同步。
#include <thread>
#include <vector>
#include <numeric>
// 每个线程处理一部分数据的求和
void partial_sum(const std::vector<int>& data, int start, int end, int& result) {
result = std::accumulate(data.begin() + start, data.begin() + end, 0);
}
int main() {
std::vector<int> nums(1000, 1);
int thread_num = 4;
int part_size = nums.size() / thread_num;
std::vector<int> partial_results(thread_num, 0);
std::vector<std::thread> threads;
for (int i = 0; i < thread_num; i++) {
int start = i * part_size;
int end = (i == thread_num - 1) ? nums.size() : start + part_size;
threads.emplace_back(partial_sum, std::ref(nums), start, end, std::ref(partial_results[i]));
}
for (auto& t : threads) {
t.join();
}
int total = std::accumulate(partial_results.begin(), partial_results.end(), 0);
std::cout << "Total sum: " << total << std::endl;
return 0;
}无锁编程的应用
对于竞争非常激烈的场景,锁的开销会变得很明显,这时候可以考虑使用无锁数据结构或者原子操作。C++11之后提供了std::atomic模板,支持原子操作,不需要加锁就能保证操作的线程安全。
#include <atomic>
#include <thread>
#include <iostream>
std::atomic<int> g_atomic_counter(0);
void atomic_increment() {
for (int i = 0; i < 1000; i++) {
// 原子自增操作,无锁
g_atomic_counter++;
}
}
int main() {
std::thread t1(atomic_increment);
std::thread t2(atomic_increment);
t1.join();
t2.join();
std::cout << "Final counter: " << g_atomic_counter << std::endl;
return 0;
}需要注意的是,无锁编程的实现难度较高,容易出现隐蔽的线程安全问题,只有在锁竞争严重影响性能的场景下才建议使用,并且要充分测试。
优化线程调度和亲和性
线程的频繁切换也会带来性能开销,可以通过设置线程亲和性,将线程绑定到固定的CPU核心上,减少线程切换和缓存失效的问题。在Linux系统下可以使用pthread_setaffinity_np函数设置线程亲和性。
#include <thread>
#include <pthread.h>
#include <iostream>
void set_thread_affinity(std::thread& t, int cpu_core) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu_core, &cpuset);
pthread_t native_thread = t.native_handle();
pthread_setaffinity_np(native_thread, sizeof(cpu_set_t), &cpuset);
}
void thread_work() {
std::cout << "Thread running on bound core" << std::endl;
}
int main() {
std::thread t(thread_work);
// 将线程绑定到0号CPU核心
set_thread_affinity(t, 0);
t.join();
return 0;
}另外,要合理控制线程数量,线程数量不是越多越好,一般建议线程数量和CPU核心数量相当,避免过多的线程导致频繁的上下文切换。
其他优化注意事项
还要注意避免伪共享问题,当多个线程修改的变量位于同一个缓存行时,会导致缓存频繁失效,影响性能。可以通过缓存行对齐的方式解决这个问题,比如使用alignas关键字将变量对齐到缓存行大小。
同时,在调试多线程性能问题时,可以使用性能分析工具,比如Linux下的perf、valgrind的helgrind工具,定位锁竞争、线程阻塞等性能瓶颈,针对性地进行优化。