1. C++多线程编程基础
现代C++(C++11及以上版本)通过标准库提供了强大的多线程支持,使得开发者能够轻松创建和管理线程。在C++中,线程是程序执行的最小单元,多个线程可以并发执行,共享进程的资源(如内存空间、文件描述符等),但每个线程拥有独立的栈空间和程序计数器。
1.1 线程创建的基本方式
C++11提供了三种主要的线程创建方式,每种方式都有其适用场景:
- 函数指针方式:最基础的线程创建方法,适合简单的独立函数
cpp复制#include <iostream>
#include <thread>
void threadFunction(int num) {
std::cout << "Thread running with number: " << num << std::endl;
}
int main() {
std::thread t1(threadFunction, 42);
t1.join();
return 0;
}
- 函数对象方式:通过重载operator()的类创建线程,适合需要维护状态的线程
cpp复制class ThreadTask {
public:
void operator()(int x) const {
std::cout << "Processing value: " << x << std::endl;
}
};
int main() {
std::thread t2(ThreadTask(), 100);
t2.join();
return 0;
}
- Lambda表达式方式:现代C++中最常用的方式,简洁直观
cpp复制int main() {
std::thread t3([](){
std::cout << "Lambda thread running" << std::endl;
});
t3.join();
return 0;
}
注意:创建线程后必须调用join()或detach(),否则程序终止时会调用std::terminate()。join()会阻塞当前线程直到目标线程完成,而detach()会让线程在后台独立运行。
1.2 线程管理与生命周期
线程对象的管理有几个关键点需要注意:
- 线程标识:每个线程有唯一的标识符,可通过get_id()获取
cpp复制std::thread::id this_id = std::this_thread::get_id();
- 硬件并发性:hardware_concurrency()返回理论上可并行的线程数
cpp复制unsigned int n = std::thread::hardware_concurrency();
- 线程移动语义:std::thread不可复制但可移动
cpp复制std::thread t4(threadFunction, 10);
std::thread t5 = std::move(t4); // 正确
// std::thread t6 = t4; // 错误!不能复制
- 线程休眠:可以使用sleep_for或sleep_until
cpp复制std::this_thread::sleep_for(std::chrono::seconds(1));
2. 线程同步机制
多线程编程中最关键的挑战之一是处理共享资源的访问。C++提供了多种同步原语来保证线程安全。
2.1 互斥量(Mutex)
互斥量是最基本的同步机制,用于保护共享数据不被同时访问:
- 基本mutex使用:
cpp复制std::mutex mtx;
int shared_data = 0;
void increment() {
mtx.lock();
++shared_data;
mtx.unlock();
}
- RAII风格的锁管理(推荐):
cpp复制void safe_increment() {
std::lock_guard<std::mutex> lock(mtx);
++shared_data;
// 离开作用域自动解锁
}
- 更灵活的unique_lock:
cpp复制void process_data() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
// 做一些不涉及共享数据的操作
lock.lock();
// 处理共享数据
lock.unlock(); // 可以手动解锁
}
2.2 条件变量(Condition Variable)
条件变量用于线程间的通信,允许线程等待特定条件成立:
cpp复制std::mutex mtx;
std::condition_variable cv;
bool data_ready = false;
// 生产者线程
void producer() {
std::lock_guard<std::mutex> lock(mtx);
// 准备数据
data_ready = true;
cv.notify_one(); // 通知一个等待的线程
}
// 消费者线程
void consumer() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return data_ready; }); // 等待条件成立
// 处理数据
}
2.3 原子操作(Atomic)
对于简单的数据类型,原子操作提供了无锁的线程安全访问:
cpp复制#include <atomic>
std::atomic<int> counter(0);
void increment_atomic() {
counter.fetch_add(1, std::memory_order_relaxed);
}
原子操作支持多种内存顺序模型:
- memory_order_relaxed:最宽松,只保证原子性
- memory_order_acquire/consume:保证读操作的顺序
- memory_order_release:保证写操作的顺序
- memory_order_acq_rel:同时保证读写顺序
- memory_order_seq_cst:最严格,保证全局顺序(默认)
3. 高级线程技术
3.1 线程局部存储(TLS)
使用thread_local关键字可以让每个线程拥有变量的独立副本:
cpp复制thread_local int thread_specific_value = 0;
void thread_func() {
++thread_specific_value;
std::cout << "Thread value: " << thread_specific_value << std::endl;
}
int main() {
std::thread t1(thread_func);
std::thread t2(thread_func);
t1.join();
t2.join();
return 0;
}
// 输出可能是:
// Thread value: 1
// Thread value: 1
3.2 Future和Promise
用于线程间的异步结果传递:
cpp复制#include <future>
int compute_value() { return 42; }
int main() {
std::future<int> result = std::async(std::launch::async, compute_value);
std::cout << "Result: " << result.get() << std::endl;
return 0;
}
Promise允许显式设置值:
cpp复制void set_value(std::promise<int> prom) {
prom.set_value(10);
}
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t(set_value, std::move(prom));
std::cout << "Value: " << fut.get() << std::endl;
t.join();
return 0;
}
3.3 并行算法(C++17)
C++17引入了并行执行策略:
cpp复制#include <algorithm>
#include <vector>
#include <execution>
int main() {
std::vector<int> v(1000);
std::iota(v.begin(), v.end(), 0);
// 并行排序
std::sort(std::execution::par, v.begin(), v.end());
// 并行变换
std::for_each(std::execution::par, v.begin(), v.end(),
[](int& n) { n *= 2; });
return 0;
}
4. 多线程编程实践与陷阱
4.1 常见问题与解决方案
-
数据竞争:多个线程同时访问共享数据且至少有一个是写操作
- 解决方案:使用互斥量、原子操作或线程局部存储
-
死锁:多个线程互相等待对方释放资源
- 避免方法:
- 总是以相同顺序获取锁
- 使用std::lock同时锁定多个互斥量
- 设置锁超时(std::timed_mutex)
- 避免方法:
cpp复制std::mutex mtx1, mtx2;
// 错误的锁顺序可能导致死锁
void thread1() {
mtx1.lock();
mtx2.lock();
// ...
mtx2.unlock();
mtx1.unlock();
}
void thread2() {
mtx2.lock(); // 与thread1顺序相反
mtx1.lock();
// ...
mtx1.unlock();
mtx2.unlock();
}
// 正确的做法:使用std::lock
void safe_operation() {
std::lock(mtx1, mtx2); // 同时锁定,避免死锁
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
// ...
}
- 虚假唤醒:条件变量可能在未被通知的情况下返回
- 解决方案:总是使用谓词检查条件
cpp复制// 不安全的等待
cv.wait(lock);
// 安全的等待(推荐)
cv.wait(lock, []{ return condition; });
4.2 性能优化技巧
- 减少锁的粒度:使用更细粒度的锁或读写锁(std::shared_mutex)
cpp复制std::shared_mutex smtx;
void reader() {
std::shared_lock<std::shared_mutex> lock(smtx);
// 多个读取者可以同时访问
}
void writer() {
std::unique_lock<std::shared_mutex> lock(smtx);
// 只有一个写入者可以访问
}
-
避免锁争用:
- 使用无锁数据结构
- 减少临界区大小
- 使用线程本地存储
-
任务并行而非数据并行:将工作划分为独立任务而非数据块
4.3 线程池实现
虽然标准库没有直接提供线程池,但可以自己实现一个简单的版本:
cpp复制#include <vector>
#include <queue>
#include <functional>
#include <condition_variable>
class ThreadPool {
public:
ThreadPool(size_t threads) : stop(false) {
for(size_t i = 0; i < 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>
void enqueue(F&& f) {
{
std::unique_lock<std::mutex> lock(queue_mutex);
tasks.emplace(std::forward<F>(f));
}
condition.notify_one();
}
~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;
};
使用示例:
cpp复制int main() {
ThreadPool pool(4);
for(int i = 0; i < 8; ++i) {
pool.enqueue([i] {
std::cout << "Task " << i << " running\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Task " << i << " done\n";
});
}
return 0;
}
5. C++20中的线程新特性
C++20引入了一些新的多线程相关特性:
- std::jthread:可自动join的线程
cpp复制void worker() { /*...*/ }
int main() {
std::jthread t(worker); // 析构时自动join
return 0;
}
- 停止令牌(stop_token):支持协作式线程取消
cpp复制void worker(std::stop_token stoken) {
while(!stoken.stop_requested()) {
// 执行工作
}
}
int main() {
std::jthread t(worker);
// ...
t.request_stop(); // 请求停止
return 0;
}
- 信号量(semaphore)和latch/barrier:新的同步原语
cpp复制#include <semaphore>
std::counting_semaphore<10> sem(0);
void worker() {
sem.acquire();
// 执行工作
}
int main() {
std::jthread t(worker);
sem.release(); // 允许worker继续
return 0;
}
在实际项目中,选择合适的多线程技术需要考虑:
- 性能需求
- 代码复杂度
- 可维护性
- 目标平台特性
对于简单的并行任务,std::async通常是最简单的选择;对于高性能需求,可能需要直接使用std::thread和同步原语;对于复杂系统,可以考虑使用第三方线程池或任务调度库。
