在现代计算机体系结构中,多核处理器已经成为主流。为了充分发挥硬件性能,多线程编程显得尤为重要。C++ 作为一门强大而灵活的系统级语言,从 C++11 开始正式引入了标准化的线程库,为开发者提供了创建、管理线程及同步资源的现代化接口。
本文将全面系统地讲解 C++ 多线程编程,包括基础线程操作、数据共享、互斥锁、条件变量、异步任务、原子操作及并发容器等内容,并通过多个实战示例加深理解。
一、C++ 中的线程模型
1.1 什么是线程?
线程是操作系统能够调度的最小执行单元,是进程中的一个执行路径。一个进程可以包含多个线程,它们共享同一个内存空间,但每个线程有自己独立的栈。
1.2 C++ 的线程支持
从 C++11 开始,C++ 标准库提供了如下线程支持:
std::thread
:创建与管理线程std::mutex
/std::lock_guard
:互斥量与自动加锁std::condition_variable
:条件变量std::future
/std::async
:异步任务std::atomic
:原子类型,支持无锁编程
二、线程的基本使用
2.1 创建线程
#include <iostream>
#include <thread>
void hello() {
std::cout << "Hello from thread!" << std::endl;
}
int main() {
std::thread t(hello);
t.join(); // 等待线程结束
return 0;
}
2.2 传递参数给线程
void greet(std::string name) {
std::cout << "Hello, " << name << std::endl;
}
std::thread t(greet, "Alice");
注意:需要传引用时需用 std::ref
包装。
void modify(int& x) { x += 10; }
int value = 5;
std::thread t(modify, std::ref(value));
2.3 join 与 detach
join()
:等待线程完成,阻塞调用线程。detach()
:分离线程,后台运行,生命周期由系统管理。
std::thread t(f);
t.detach(); // 不再受 main 控制,需确保线程中不使用已销毁资源
三、共享数据与互斥机制
3.1 多线程共享数据的问题
多个线程对同一资源进行读写操作时,可能导致数据竞争(race condition)。
int counter = 0;
void increment() {
for (int i = 0; i < 10000; ++i) {
++counter;
}
}
多个线程同时修改 counter
,结果不确定。
3.2 使用 mutex 加锁
#include <mutex>
std::mutex mtx;
void safe_increment() {
for (int i = 0; i < 10000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
++counter;
}
}
3.3 lock_guard 与 unique_lock
std::lock_guard
:简单的 RAII 锁管理std::unique_lock
:支持延迟锁定、手动解锁、条件变量使用
std::unique_lock<std::mutex> lock(mtx);
lock.unlock(); // 可手动释放
四、条件变量与线程通信
4.1 生产者消费者模型
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> buffer;
void producer() {
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
buffer.push(i);
cv.notify_one();
}
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !buffer.empty(); });
int item = buffer.front();
buffer.pop();
std::cout << "Consumed: " << item << std::endl;
}
}
4.2 notify_one vs notify_all
notify_one()
:唤醒一个等待线程notify_all()
:唤醒所有等待线程
五、异步编程与 future/promise
5.1 std::async
#include <future>
int compute() {
return 42;
}
int main() {
std::future<int> fut = std::async(compute);
int result = fut.get(); // 阻塞直到返回值准备好
}
5.2 使用 promise 传递值
void worker(std::promise<int> p) {
p.set_value(123);
}
std::promise<int> p;
std::future<int> f = p.get_future();
std::thread t(worker, std::move(p));
int val = f.get();
六、原子操作与无锁编程
6.1 std::atomic 基本用法
#include <atomic>
std::atomic<int> acounter(0);
void task() {
for (int i = 0; i < 10000; ++i)
acounter.fetch_add(1);
}
常用方法:
fetch_add
,fetch_sub
compare_exchange_weak/strong
load
,store
6.2 原子与内存序
std::memory_order
控制内存同步行为:
memory_order_relaxed
:不保证同步memory_order_acquire/release
:适度同步memory_order_seq_cst
:最严格同步,默认
七、并发容器与线程安全 STL
7.1 为什么要并发容器?
STL 中的容器如 vector
、map
等并不是线程安全的。
并发容器如:
concurrent_queue
concurrent_vector
(TBB 或 boost)std::atomic<T*>
实现的无锁链表等
7.2 自己实现线程安全队列
template <typename T>
class ThreadSafeQueue {
std::queue<T> q;
std::mutex mtx;
std::condition_variable cv;
public:
void push(T value) {
std::lock_guard<std::mutex> lock(mtx);
q.push(std::move(value));
cv.notify_one();
}
T pop() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&]{ return !q.empty(); });
T val = std::move(q.front());
q.pop();
return val;
}
};
八、实战案例:并发网页下载器
8.1 设计目标
- 支持多个 URL 同时下载
- 下载结果保存到 map 中
- 控制最大线程数量(线程池模型)
8.2 关键代码
#include <thread>
#include <map>
#include <mutex>
#include <vector>
#include <curl/curl.h>
std::mutex mtx;
std::map<std::string, std::string> result;
void download(const std::string& url) {
CURL* curl = curl_easy_init();
std::string data;
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
[](char* ptr, size_t size, size_t nmemb, void* userdata) -> size_t {
std::string* s = static_cast<std::string*>(userdata);
s->append(ptr, size * nmemb);
return size * nmemb;
});
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
std::lock_guard<std::mutex> lock(mtx);
result[url] = data;
}
主程序并发控制:
std::vector<std::string> urls = {"http://a.com", "http://b.com"};
std::vector<std::thread> threads;
for (const auto& url : urls) {
threads.emplace_back(download, url);
}
for (auto& t : threads) t.join();
九、C++20 中的并发增强
9.1 std::jthread
自动 join 的线程,避免遗忘 join()
。
std::jthread t([]{ std::cout << "run\n"; });
9.2 stop_token 与可中断线程
std::jthread t([](std::stop_token st) {
while (!st.stop_requested()) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
});
t.request_stop();
十、总结与最佳实践
10.1 总结要点
技术点 | 建议 |
线程创建 | 使用 |
资源共享 |
|
同步控制 |
|
异步任务 |
|
原子操作 | 使用 |
并发容器 | 自实现或使用库 |
线程数量控制 | 推荐封装线程池 |
10.2 最佳实践建议
- 不要过度创建线程,推荐线程池
- 多线程代码要尽可能保持无共享状态(状态隔离)
- 善用 RAII +
lock_guard
管理锁 - 推荐使用
std::async
编写并行任务 - 合理选择同步机制:
mutex
vsatomic
vscondition_variable