C++中的"线程"通常指单个执行流(如std::thread
对象),而"多线程"指程序中同时存在多个这样的执行流,并涉及它们的创建、管理和同步。实现跨线程操作的核心在于安全地处理共享数据和线程间通信。
以下是实现跨线程操作的主要方法:
1. 🛡️ 使用互斥锁 (Mutex) 保护共享数据
互斥锁用于确保同一时间只有一个线程能访问共享资源,防止数据竞争。
-
std::mutex
: 基本互斥锁,需手动管理锁的获取和释放。#include <iostream>#include <thread>#include <mutex>std::mutex mtx;// 全局互斥量int shared_data = 0;void increment() {mtx.lock();// 请求锁定互斥量++shared_data;// 修改共享资源mtx.unlock();// 释放互斥量 }
更推荐使用RAII机制自动管理锁:
-
std::lock_guard
: 在构造时加锁,析构时自动解锁,适用于简单作用域。void safe_increment() {std::lock_guard<std::mutex> lock(mtx);// 自动锁定和解锁++shared_data; }// lock_guard析构,mtx自动解锁
-
std::unique_lock
: 比lock_guard
更灵活,支持延迟加锁、条件变量等。void safe_increment_flex() {std::unique_lock<std::mutex> ul(mtx);++shared_data;ul.unlock();// 可手动提前解锁// ... 执行其他不操作共享数据的任务 }
2. 🔄 使用条件变量 (Condition Variable) 进行线程间协调
条件变量允许一个或多个线程等待某个条件成立后再继续执行,常用于生产者-消费者模型。
-
std::condition_variable
: 线程等待特定条件被满足。#include <iostream>#include <thread>#include <mutex>#include <condition_variable>#include <queue>std::mutex mtx; std::condition_variable cv; std::queue<int> data_queue; bool ready = false;// 生产者线程void producer() {std::this_thread::sleep_for(std::chrono::seconds(1));{std::lock_guard<std::mutex> lk(mtx);data_queue.push(42);ready = true;}cv.notify_one();// 通知一个等待的消费者线程 }// 消费者线程void consumer() {std::unique_lock<std::mutex> lk(mtx); // 等待条件满足:lambda函数返回false时,线程会阻塞并释放锁cv.wait(lk, []{ return ready; }); // 条件满足后,重新获得锁,继续执行int data = data_queue.front();data_queue.pop();std::cout << "Consumed: " << data << std::endl; }int main() {std::thread prod(producer);std::thread cons(consumer);prod.join();cons.join();return 0; }
3. ⚡ 使用原子操作 (Atomic Operations)
原子操作确保对基本数据类型(如int
, bool
)的读写操作是不可中断的,无需加锁,性能更高。
-
std::atomic
: 模板类,提供原子操作。#include <atomic>#include <thread>std::atomic<int> atomic_counter(0);void safe_increment_atomic() {for (int i = 0; i < 1000; ++i) {++atomic_counter;// 原子操作,线程安全} }int main() {std::thread t1(safe_increment_atomic);std::thread t2(safe_increment_atomic);t1.join();t2.join();std::cout << atomic_counter << std::endl;// 输出2000return 0; }
4. 🚀 使用异步操作 (Async Operations)
std::async
和std::future
/std::promise
可以更方便地获取另一个线程的执行结果,实现线程间数据传递。
-
std::async
&std::future
: 异步执行任务并获取返回值。#include <iostream>#include <future>int heavy_calculation() { // 模拟耗时计算return 42; }int main() { // 异步启动任务,返回一个future对象std::future<int> fut = std::async(std::launch::async, heavy_calculation); // ... 主线程可以同时做其他工作int result = fut.get();// 获取异步任务的结果(如果未完成会等待)std::cout << "Result: " << result << std::endl;return 0; }
-
std::promise
&std::future
: 在线程间传递数据。#include <iostream>#include <thread>#include <future>void worker(std::promise<int> prom) { // 做一些工作...prom.set_value(123);// 设置值,通知future }int main() {std::promise<int> prom;std::future<int> fut = prom.get_future();std::thread t(worker, std::move(prom));int value = fut.get();// 等待并获取worker线程设置的值std::cout << "Received: " << value << std::endl;t.join();return 0; }
📊 方法对比与选择建议
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
互斥锁 (Mutex) | 保护复杂的共享数据结构或代码段 | 通用性强,可保护任意临界区 | 锁竞争可能导致性能开销和死锁风险 |
条件变量 (Condition Variable) | 线程间等待特定条件发生(生产者-消费者) | 高效协调线程执行顺序 | 需与互斥锁配合使用,逻辑稍复杂 |
原子操作 (Atomic) | 简单的计数器、状态标志更新 | 高性能,无锁编程 | 仅适用于基本数据类型,逻辑复杂时易出错 |
异步操作 (Async/Future) | 需要获取另一个线程的结果 | 简化线程间值传递和同步 | 不适合需要持续通信或复杂同步的场景 |
选择哪种方法取决于你的具体需求:
- 需要对复杂数据结构或代码块进行排他性访问时,使用互斥锁(优先搭配
lock_guard
或unique_lock
)。 - 需要让线程等待某个条件成立(如任务队列非空)时,使用条件变量。
- 只需安全地修改一个整数、布尔值等简单变量时,使用原子操作。
- 需要获取另一个线程的计算结果时,使用**
std::async
和std::future
**。
💡 重要注意事项
- 避免死锁:确保锁的获取和释放顺序一致,或使用
std::lock
一次性锁定多个互斥量。 - 线程管理:使用
join()
等待线程完成,或使用detach()
分离线程(需谨慎,分离后无法再控制线程)。 - 数据传递:向线程传递引用参数时,需使用
std::ref
(确保对象生命周期长于线程),或直接按值传递。
实现安全高效的跨线程操作,关键在于根据场景选择合适的同步机制,并谨慎管理共享数据的访问。