在C++并发编程中,线程间通信指的是不同线程之间交换数据、同步执行状态的过程,合理的通信机制是保证并发程序正确运行的基础,C++标准库提供了多种工具支持线程间通信,开发者可以根据实际场景选择合适的方案。

基于互斥锁与条件变量的通信方式
互斥锁用于保证同一时间只有一个线程访问共享资源,条件变量则可以让线程在某个条件不满足时阻塞等待,直到其他线程通知条件满足后再继续执行,二者配合是实现线程间通信的常用方案。
下面通过一个生产者消费者模型的示例展示这种通信方式的实现:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::queue<int> data_queue; // 共享队列,存储生产的数据
std::mutex queue_mutex; // 保护共享队列的互斥锁
std::condition_variable data_cond; // 用于线程间通知的条件变量
// 生产者线程函数,向队列中放入数据
void producer(int count) {
for (int i = 0; i < count; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产耗时
std::lock_guard<std::mutex> lock(queue_mutex); // 加锁保护共享队列
data_queue.push(i);
std::cout << "生产者生产数据:" << i << std::endl;
data_cond.notify_one(); // 通知一个等待的消费者线程
}
}
// 消费者线程函数,从队列中取出数据
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(queue_mutex); // 加锁,unique_lock支持条件变量的等待操作
// 等待条件满足:队列不为空,避免虚假唤醒
data_cond.wait(lock, []{ return !data_queue.empty(); });
int data = data_queue.front();
data_queue.pop();
lock.unlock(); // 取出数据后提前解锁,减少锁持有时间
std::cout << "消费者消费数据:" << data << std::endl;
if (data == 9) { // 假设生产10个数据后结束
break;
}
}
}
int main() {
std::thread prod(producer, 10);
std::thread cons(consumer);
prod.join();
cons.join();
return 0;
}使用这种方式需要注意避免虚假唤醒,等待条件变量时一定要传入判断条件的谓词,同时尽量缩小锁的持有范围,减少线程阻塞时间。
基于原子操作的通信方式
原子操作指的是不可被中断的一个或一系列操作,C++的std::atomic模板提供了原子类型,适合用于简单的状态同步场景,比如用一个原子变量标记线程是否需要停止。
以下是使用原子变量实现线程停止通知的示例:
#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>
std::atomic<bool> stop_flag(false); // 原子停止标志,初始为false
// 工作线程函数,循环执行任务直到收到停止通知
void worker() {
while (!stop_flag.load()) { // 原子读取停止标志
std::cout << "工作线程执行任务中..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
std::cout << "工作线程收到停止通知,退出执行" << std::endl;
}
int main() {
std::thread t(worker);
std::this_thread::sleep_for(std::chrono::seconds(1)); // 主线程等待1秒
stop_flag.store(true); // 原子修改停止标志,通知工作线程停止
t.join();
return 0;
}原子操作的开销比互斥锁小,但只适合简单的变量读写同步,无法处理复杂的共享资源访问控制场景。
基于future与promise的通信方式
std::promise和std::future用于在线程之间传递单个结果,一个线程通过promise设置值,另一个线程通过对应的future获取值,适合一次性结果传递的场景。
以下示例展示父线程获取子线程计算结果的通信过程:
#include <iostream>
#include <thread>
#include <future>
// 子线程函数,计算1到n的和,通过promise返回结果
void calculate_sum(int n, std::promise<int> result_promise) {
int sum = 0;
for (int i = 1; i <= n; ++i) {
sum += i;
}
result_promise.set_value(sum); // 设置结果,通知获取方
}
int main() {
std::promise<int> sum_promise;
std::future<int> sum_future = sum_promise.get_future(); // 获取对应的future
std::thread calc_thread(calculate_sum, 100, std::move(sum_promise)); // 启动子线程,转移promise所有权
int result = sum_future.get(); // 阻塞等待子线程返回结果
std::cout << "1到100的和为:" << result << std::endl;
calc_thread.join();
return 0;
}这种方式只能传递一次结果,如果需要多次传递数据,需要结合其他通信方式使用。
不同通信方式的适用场景对比
为了帮助开发者选择合适的通信方式,以下是几种常见方案的对比:
| 通信方式 | 适用场景 | 优势 | 局限性 |
|---|---|---|---|
| 互斥锁+条件变量 | 复杂的共享资源访问、多数据传递、线程状态同步 | 功能全面,支持复杂场景 | 使用复杂度高,容易出现死锁、虚假唤醒等问题 |
| 原子操作 | 简单的状态标记、单个变量的同步 | 开销小,使用简单 | 仅支持简单变量,无法处理复杂共享资源 |
| future+promise | 一次性结果传递、线程间单次数据交互 | 语义清晰,避免共享资源的复杂管理 | 只能传递一次结果,不支持多次通信 |
在实际开发中,开发者可以根据通信的频率、数据复杂度、同步需求选择合适的方案,也可以组合使用多种通信方式满足复杂场景的需求。