C++多线程编程入门实战
本文旨在帮助初学者快速理解和跑通C++多线程编程,涵盖从基础概念到实际应用的完整路径。
第一部分:核心概念与准备工作
多线程编程的重要性
在多核处理器成为主流的今天,多线程编程是提高程序性能的关键技术。它允许程序同时执行多个任务,充分利用硬件资源。
C++多线程开发环境配置
- C++11及以上版本:确保你的编译器支持C++11或更高标准
- 编译器选项:使用
-std=c++11
(或更高)和-pthread
标志编译 - 头文件:
#include <thread>
,#include <mutex>
,#include <atomic>
初学者建议
- 从单线程开始:先实现正确的单线程版本
- 理解数据竞争:多个线程同时访问共享数据的危险性
- 掌握同步机制:互斥锁、原子操作等
- 避免死锁:注意锁的获取和释放顺序
第二部分:基础代码示例
示例1:单线程 vs 多线程基础
a) 单线程版本
#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;
}
编译命令:
g++ -std=c++11 -pthread single_thread.cpp -o single_thread
b) 多线程版本
#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;
}
编译命令:
g++ -std=c++11 -pthread multi_thread.cpp -o multi_thread
关键点说明:
std::thread
创建新线程emplace_back
直接在线程向量中构造线程对象join()
等待线程执行完成- 输出中线程ID不同,表明任务在不同线程执行
示例2:数据竞争与互斥锁解决方案
a) 存在数据竞争的代码
#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) 使用互斥锁的正确版本
#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_guardshared_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_lockshared_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:原子操作 - 无锁解决方案
#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:条件变量实现生产者-消费者模式
#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. 线程池基础实现
#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++开发中的重要技能,虽然有一定难度,但通过循序渐进的学习和实践,完全可以掌握。建议从简单的示例开始,逐步深入理解同步机制,最终能够设计出高效、安全的并发程序。