文章目录
前言
在当今计算领域,无论是追求极致响应速度的桌面应用、处理海量并发请求的服务器后端,还是需要实时处理数据的科学计算与游戏引擎,多线程技术 已然成为提升软件性能与用户体验的核心手段。它通过"分而治之"的策略,将应用程序的任务负载分配到多个执行流中 ,从而最大限度地挖掘现代多核处理器的并行计算潜力,对于提高软件的流畅度、响应能力和整体执行效率具有不可替代的重要作用。
本文旨在系统地介绍C++语言中的多线程编程,并给出案例。
多线程的核心价值
-
提升性能与吞吐量:在拥有多个CPU核心的系统中,单线程程序只能利用其中一个核心,造成巨大的计算资源浪费。多线程程序可以将计算密集型任务(如图像处理、数据编码、物理模拟)分解成多个子任务,并由多个线程并行执行,从而显著缩短任务总耗时,实现近乎线性的性能加速。
-
增强响应性与流畅度:在图形用户界面(GUI)应用程序中,如果将耗时操作(如文件读写、网络请求)放在主线程(通常是UI线程)中执行,会导致界面"冻结",无法响应用户操作。通过创建后台工作线程来处理这些阻塞性任务,可以确保UI线程始终保持流畅的交互响应,从而极大提升用户体验。
-
简化异步任务模型:对于需要同时处理多个I/O操作(如网络通信、数据库访问)的服务端程序,多线程模型比传统的异步回调模型更直观、更易于理解和编码。每个连接可以分配一个独立的线程,使得代码逻辑清晰,接近于同步编程的思维方式。
C++多线程编程基础
在C++11标准之前,多线程编程严重依赖平台特定的API。C++11的引入将多线程支持纳入标准库,带来了可移植且类型安全的线程管理工具。
创建线程并启动
cpp
#include <iostream>
#include <thread>
// 1. 普通函数作为线程入口
void background_task(int id) {
std::cout << "线程 " << id << " 正在执行,线程ID: "
<< std::this_thread::get_id() << std::endl;
}
// 2. Lambda表达式作为线程入口
auto lambda_task = [](const std::string& message) {
std::cout << "Lambda线程: " << message << std::endl;
};
int main() {
// 创建并启动线程
std::thread t1(background_task, 1); // 传递函数指针和参数
std::thread t2(lambda_task, "Hello from Lambda!"); // 传递Lambda和参数
// 等待线程完成 (重要!)
t1.join(); // 主线程阻塞,直到t1执行完毕
t2.join(); // 主线程阻塞,直到t2执行完毕
std::cout << "主线程结束。" << std::endl;
return 0;
}
输出:
线程同步机制
当多个线程需要访问共享数据或资源时,如果不加控制,就会引发数据竞争,导致程序行为不确定、崩溃或产生错误结果。线程同步机制正是为了解决这一问题而生的,有以下几种方式:
1. 互斥锁 :std::mutex
互斥锁 是最基本的同步原语,它保证了同一时间只有一个线程可以进入被保护的代码段(临界区)。
cpp
#include <thread>
#include <mutex>
#include <vector>
#include <iostream>
std::mutex g_mutex; // 全局互斥锁
int shared_counter = 0;
void increment_counter(int iterations) {
for (int i = 0; i < iterations; ++i) {
g_mutex.lock(); // 进入临界区前加锁
++shared_counter; // 安全地修改共享数据
g_mutex.unlock(); // 离开临界区后解锁
}
}
int main() {
std::thread t1(increment_counter, 100000);
std::thread t2(increment_counter, 100000);
t1.join();
t2.join();
std::cout << "最终计数器值: " << shared_counter << std::endl; // 正确输出 200000
return 0;
}
输出:

这种方式可能存在的问题 :直接使用lock()和unlock()容易因异常或提前返回而导致锁无法释放,造成死锁。
2. 锁守卫 :std::lock_guard
和 std::unique_lock
,可在一定程度上解决上述死锁问题
区别:
lock_guard
是基于互斥锁std::mutex
实现的,unique_lock
是基于通用锁 std::unique_lock 实现。unique_lock
可以实现比lock_guard
更灵活的锁操作:lock_guard
是不可移动的(moveable),即不能拷贝、赋值、移动,只能通过构造函数初始化和析构函数销毁,unique_lock
是可移动的,可以拷贝、赋值、移动。unique_lock
提供了更多的控制锁的行为,比如锁超时、不锁定、条件变量等。ORB-SLAM算法中常用这个。unique_lock
比lock_guard
更重,因为它有更多的功能,更多的开销。如果只需要简单的互斥保护,使用lock_guard
更好。
lock_guard 案例:
cpp
#include <thread>
#include <mutex>
#include <vector>
#include <iostream>
std::mutex g_mutex; // 全局互斥锁
int shared_counter = 0;
void safe_increment(int iterations) {
for (int i = 0; i < iterations; ++i) {
std::lock_guard<std::mutex> lock(g_mutex); // 构造即加锁
++shared_counter;
// lock 析构时自动解锁
}
}
int main() {
// 使用安全的增量函数
shared_counter = 0; // 重置计数器
std::thread t1(safe_increment, 100000);
std::thread t2(safe_increment, 100000);
t1.join();
t2.join();
std::cout << "最终计数器值: " << shared_counter << std::endl; // 正确输出 200000
return 0;
}
输出
unique_lock案例:
cpp
#include <iostream>
#include <mutex>
#include <thread>
std::mutex g_mutex; // 全局互斥锁
int shared_counter = 0;
void worker(int iterations)
{
std::lock_guard<std::mutex> lg(g_mutex); // lock_guard 方式上锁
for (int i = 0; i < iterations; ++i) {
++shared_counter; // 安全地修改共享数据
}
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "worker thread is done." << std::endl;
} // lock_guard 不支持手动解锁,会在此自动释放锁
void another_worker(int iterations)
{
std::unique_lock<std::mutex> ul(g_mutex); // unique_lock 方式上锁
for (int i = 0; i < iterations; ++i) {
++shared_counter; // 安全地修改共享数据
}
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "another worker thread is done." << std::endl;
ul.unlock(); // 手动释放锁
//do something...
} // 如果锁未释放,unique_lock 会在此自动释放锁
int main()
{
std::thread t1(worker, 100000);
std::thread t2(another_worker, 100000);
t1.join();
t2.join();
std::cout << "最终计数器值: " << shared_counter << std::endl; // 正确输出 200000
return 0;
}
输出:

3. 条件变量 :std::condition_variable
条件变量用于实现线程间的等待与通知机制,允许一个线程等待某个条件成立,而另一个线程在条件改变时通知等待的线程。这是实现生产者-消费者模型等协作模式的关键。
cpp
#include <queue>
#include <condition_variable>
#include <thread>
#include <mutex>
#include <iostream>
std::queue<int> data_queue;
std::mutex queue_mutex;
std::condition_variable data_cond;
// 生产者线程
void data_producer() {
for (int i = 0; i < 10; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
{
std::lock_guard<std::mutex> lock(queue_mutex);
data_queue.push(i);
std::cout << "生产数据: " << i << std::endl;
}
data_cond.notify_one(); // 通知一个等待的消费者
}
}
// 消费者线程
void data_consumer() {
while (true) {
std::unique_lock<std::mutex> lock(queue_mutex);
// 等待条件成立:队列非空。等待时会自动释放锁,被唤醒后重新获取锁。
data_cond.wait(lock, []{ return !data_queue.empty(); });
int data = data_queue.front();
data_queue.pop();
lock.unlock(); // 尽早释放锁
std::cout << "消费数据: " << data << std::endl;
if (data == 9) break; // 结束条件
}
}
int main() {
std::thread producer(data_producer);
std::thread consumer(data_consumer);
producer.join();
consumer.join();
return 0;
}
输出

4. 原子操作 :std::atomic
对于简单的计数器、标志位等 ,使用互斥锁开销过大。C++提供了std::atomic模板,能够保证对该变量的操作是不可分割的,无需显式加锁,性能极高。
cpp
#include <atomic>
#include <thread>
#include <iostream>
std::atomic<int> atomic_counter(0);
void atomic_increment(int iterations) {
for (int i = 0; i < iterations; ++i) {
++atomic_counter; // 原子操作,线程安全
}
}
int main() {
std::thread t1(atomic_increment, 100000);
std::thread t2(atomic_increment, 100000);
t1.join();
t2.join();
// 正确输出 200000
std::cout << "最终计数器值: " << atomic_counter.load() << std::endl;
return 0;
}
输出

5. 异步线程 std::future
std::future
:提供了一种更高级的异步任务执行和结果获取方式,它抽象了线程管理的细节,让开发者更专注于任务本身。
cpp
#include <future>
#include <iostream>
#include <thread>
// 模拟一个耗时的计算任务
int compute_heavy_task() {
std::this_thread::sleep_for(std::chrono::seconds(2));
return 42;
}
int main() {
// 异步启动任务
std::future<int> result = std::async(std::launch::async, compute_heavy_task);
// ... 主线程可以同时做其他工作 ...
for(int i = 0; i < 5; ++i) {
std::cout << "主线程工作中..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
// 获取异步任务结果(如果需要,会阻塞等待)
int value = result.get();
std::cout << "异步任务结果为: " << value << std::endl;
return 0;
}

总结
多线程编程是C++开发者迈向高性能应用开发的必经之路 。它通过并行化极大地提升了软件的执行效率 ,并通过将阻塞操作移至后台显著增强了应用的流畅度 。C++标准库提供了一套强大而全面的工具集,从基础的std::thread
到关键的同步原语(mutex, condition_variable
),再到高效的std::atomic
。然而,线程引入了复杂 性,尤其是数据竞争和死锁问题。成功的关键在于深刻理解并正确运用线程同步机制。