本文旨在帮助初学者快速理解和跑通C++多线程编程,涵盖从基础概念到实际应用的完整路径。
第一部分:核心概念与准备工作
多线程编程的重要性
在多核处理器成为主流的今天,多线程编程是提高程序性能的关键技术。它允许程序同时执行多个任务,充分利用硬件资源。
C++多线程开发环境配置
- C++11及以上版本:确保你的编译器支持C++11或更高标准
- 编译器选项 :使用
-std=c++11
(或更高)和-pthread
标志编译 - 头文件 :
#include <thread>
,#include <mutex>
,#include <atomic>
初学者建议
- 从单线程开始:先实现正确的单线程版本
- 理解数据竞争:多个线程同时访问共享数据的危险性
- 掌握同步机制:互斥锁、原子操作等
- 避免死锁:注意锁的获取和释放顺序
第二部分:基础代码示例
示例1:单线程 vs 多线程基础
a) 单线程版本
cpp
#include <iostream>
#include <chrono>
#include <thread>
// 模拟耗时任务
void task(int id, int duration_ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(duration_ms));
std::cout << "Task " << id << " completed on thread: "
<< std::this_thread::get_id() << std::endl;
}
int main() {
auto start = std::chrono::high_resolution_clock::now();
// 顺序执行任务
task(1, 100);
task(2, 150);
task(3, 200);
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Single-threaded execution time: " << duration.count() << " ms" << std::endl;
return 0;
}
编译命令:
bash
g++ -std=c++11 -pthread single_thread.cpp -o single_thread
b) 多线程版本
cpp
#include <iostream>
#include <chrono>
#include <thread>
#include <vector>
void task(int id, int duration_ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(duration_ms));
std::cout << "Task " << id << " completed on thread: "
<< std::this_thread::get_id() << std::endl;
}
int main() {
auto start = std::chrono::high_resolution_clock::now();
// 创建线程数组
std::vector<std::thread> threads;
// 启动多个线程
threads.emplace_back(task, 1, 100);
threads.emplace_back(task, 2, 150);
threads.emplace_back(task, 3, 200);
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Multi-threaded execution time: " << duration.count() << " ms" << std::endl;
return 0;
}
编译命令:
bash
g++ -std=c++11 -pthread multi_thread.cpp -o multi_thread
关键点说明:
std::thread
创建新线程emplace_back
直接在线程向量中构造线程对象join()
等待线程执行完成- 输出中线程ID不同,表明任务在不同线程执行
示例2:数据竞争与互斥锁解决方案
a) 存在数据竞争的代码
cpp
#include <iostream>
#include <thread>
#include <vector>
int shared_counter = 0;
void increment_without_lock(int iterations) {
for (int i = 0; i < iterations; ++i) {
shared_counter++; // 非原子操作,存在数据竞争
}
}
int main() {
const int iterations = 100000;
std::vector<std::thread> threads;
// 创建多个线程同时增加计数器
for (int i = 0; i < 5; ++i) {
threads.emplace_back(increment_without_lock, iterations);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Expected: " << 5 * iterations << std::endl;
std::cout << "Actual: " << shared_counter << std::endl;
std::cout << "Data race occurred: " << (shared_counter != 5 * iterations ? "YES" : "NO") << std::endl;
return 0;
}
b) 使用互斥锁的正确版本
cpp
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
int shared_counter = 0;
std::mutex counter_mutex;
void increment_with_lock(int iterations) {
for (int i = 0; i < iterations; ++i) {
std::lock_guard<std::mutex> lock(counter_mutex);
shared_counter++;
}
}
void increment_with_try_lock(int iterations) {
for (int i = 0; i < iterations; ++i) {
std::unique_lock<std::mutex> lock(counter_mutex, std::try_to_lock);
if (lock.owns_lock()) {
shared_counter++;
} else {
// 处理获取锁失败的情况
std::this_thread::yield();
}
}
}
int main() {
const int iterations = 100000;
std::vector<std::thread> threads;
// 测试 lock_guard
shared_counter = 0;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(increment_with_lock, iterations);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Using lock_guard - Expected: " << 5 * iterations << std::endl;
std::cout << "Using lock_guard - Actual: " << shared_counter << std::endl;
// 清空线程向量
threads.clear();
// 测试 unique_lock with try_lock
shared_counter = 0;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(increment_with_try_lock, iterations);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Using unique_lock(try_lock) - Expected: " << 5 * iterations << std::endl;
std::cout << "Using unique_lock(try_lock) - Actual: " << shared_counter << std::endl;
return 0;
}
关键点说明:
std::mutex
提供基本的互斥功能std::lock_guard
RAII风格,自动管理锁的生命周期std::unique_lock
更灵活,支持延迟锁定、尝试锁定等
示例3:原子操作 - 无锁解决方案
cpp
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
std::atomic<int> atomic_counter(0);
void increment_atomic(int iterations) {
for (int i = 0; i < iterations; ++i) {
atomic_counter++; // 原子操作,无数据竞争
}
}
int main() {
const int iterations = 100000;
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back(increment_atomic, iterations);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Atomic counter - Expected: " << 5 * iterations << std::endl;
std::cout << "Atomic counter - Actual: " << atomic_counter << std::endl;
return 0;
}
示例4:条件变量实现生产者-消费者模式
cpp
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <chrono>
std::queue<int> data_queue;
std::mutex queue_mutex;
std::condition_variable queue_condvar;
const int MAX_ITEMS = 10;
void producer(int id) {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::unique_lock<std::mutex> lock(queue_mutex);
// 等待队列有空间
queue_condvar.wait(lock, []{ return data_queue.size() < MAX_ITEMS; });
int data = id * 100 + i;
data_queue.push(data);
std::cout << "Producer " << id << " produced: " << data << std::endl;
lock.unlock();
queue_condvar.notify_all(); // 通知消费者
}
}
void consumer(int id) {
for (int i = 0; i < 3; ++i) {
std::unique_lock<std::mutex> lock(queue_mutex);
// 等待队列有数据
queue_condvar.wait(lock, []{ return !data_queue.empty(); });
int data = data_queue.front();
data_queue.pop();
std::cout << "Consumer " << id << " consumed: " << data << std::endl;
lock.unlock();
queue_condvar.notify_all(); // 通知生产者
}
}
int main() {
std::vector<std::thread> producers;
std::vector<std::thread> consumers;
// 创建2个生产者和3个消费者
for (int i = 0; i < 2; ++i) {
producers.emplace_back(producer, i + 1);
}
for (int i = 0; i < 3; ++i) {
consumers.emplace_back(consumer, i + 1);
}
for (auto& t : producers) {
t.join();
}
for (auto& t : consumers) {
t.join();
}
return 0;
}
第三部分:进阶主题与最佳实践
1. 线程池基础实现
cpp
#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <future>
class ThreadPool {
public:
ThreadPool(size_t num_threads) : stop(false) {
for (size_t i = 0; i < num_threads; ++i) {
workers.emplace_back([this] {
for (;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
this->condition.wait(lock, [this] {
return this->stop || !this->tasks.empty();
});
if (this->stop && this->tasks.empty())
return;
task = std::move(this->tasks.front());
this->tasks.pop();
}
task();
}
});
}
}
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type> {
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex);
if(stop)
throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task](){ (*task)(); });
}
condition.notify_one();
return res;
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for (std::thread &worker : workers)
worker.join();
}
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::mutex queue_mutex;
std::condition_variable condition;
bool stop;
};
// 使用示例
int main() {
ThreadPool pool(4);
std::vector<std::future<int>> results;
for (int i = 0; i < 8; ++i) {
results.emplace_back(
pool.enqueue([i] {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Task " << i << " executed by thread "
<< std::this_thread::get_id() << std::endl;
return i * i;
})
);
}
for (auto&& result : results)
std::cout << "Result: " << result.get() << std::endl;
return 0;
}
2. 最佳实践总结
- 优先使用RAII :
std::lock_guard
,std::unique_lock
- 避免死锁 :按固定顺序获取锁,或使用
std::lock
同时锁定多个互斥量 - 合理使用原子操作:对于简单的计数器,原子操作比互斥锁更高效
- 使用条件变量进行线程间通信:避免忙等待
- 考虑使用线程池:避免频繁创建销毁线程的开销
- 异常安全:确保锁在异常情况下也能正确释放
3. 常见陷阱
- 数据竞争:未正确同步共享数据的访问
- 死锁:循环等待资源
- 活锁:线程不断改变状态但无法进展
- 优先级反转:低优先级线程持有高优先级线程需要的资源
结语
多线程编程是C++开发中的重要技能,虽然有一定难度,但通过循序渐进的学习和实践,完全可以掌握。建议从简单的示例开始,逐步深入理解同步机制,最终能够设计出高效、安全的并发程序。