std::promise
是 C++11 引入的一个类,用于在线程之间传递异步结果(值或异常)。它通常与 std::future
配合使用,std::promise
用于设置值或异常,而 std::future
用于获取这些值或异常。
下面通过一个更直观的生产者-消费者场景,展示 std::promise
如何在线程间传递结果:
示例:生产者线程计算平方,消费者线程获取结果
cpp
#include <iostream>
#include <thread>
#include <future>
#include <chrono>
// 生产者线程:计算某个数的平方,并将结果通过 promise 传递
void producer_task(std::promise<int> result_promise, int input) {
// 模拟耗时计算
std::this_thread::sleep_for(std::chrono::seconds(1));
// 计算平方
int square = input * input;
// 将结果设置到 promise 中(传递到消费者线程)
result_promise.set_value(square);
}
// 消费者线程:等待结果并处理
void consumer_task(std::future<int> result_future) {
// 阻塞等待结果(从生产者线程获取)
int result = result_future.get();
// 处理结果
std::cout << "消费者线程收到结果: " << result << std::endl;
}
int main() {
// 1. 创建 promise 和 future
std::promise<int> prom;
std::future<int> fut = prom.get_future();
// 2. 启动生产者线程(计算 5 的平方)
std::thread producer(producer_task, std::move(prom), 5);
// 3. 启动消费者线程(处理结果)
std::thread consumer(consumer_task, std::move(fut));
// 等待线程结束
producer.join();
consumer.join();
return 0;
}
代码解释
1. 生产者线程 (producer_task
)
- 输入 :接收一个
std::promise<int>
和一个整数input
。 - 操作 :
- 模拟耗时计算(1秒)。
- 计算
input
的平方。 - 将结果通过
promise.set_value()
设置到 promise 中。
- 作用:将计算结果传递给其他线程。
2. 消费者线程 (consumer_task
)
- 输入 :接收一个
std::future<int>
。 - 操作 :
- 调用
future.get()
阻塞等待结果。 - 获取结果后打印。
- 调用
- 作用:从其他线程获取结果并进行处理。
3. 主线程
- 创建
std::promise<int>
和关联的std::future<int>
。 - 将
promise
传递给生产者线程,将future
传递给消费者线程。 - 等待两个线程完成。
关键点
-
线程间结果传递:
- 生产者线程通过
promise.set_value()
设置结果。 - 消费者线程通过
future.get()
获取结果。
- 生产者线程通过
-
所有权转移:
std::promise
和std::future
不可复制,但可通过std::move
转移所有权。- 主线程将
promise
移入生产者线程,future
移入消费者线程。
-
同步机制:
future.get()
会阻塞消费者线程,直到生产者线程调用promise.set_value()
。
输出结果
程序运行后输出:
消费者线程收到结果: 25
更贴近实际的应用场景
假设生产者线程需要从网络下载数据,消费者线程需要处理数据:
cpp
void downloader(std::promise<std::string> data_promise) {
// 模拟下载数据
std::this_thread::sleep_for(std::chrono::seconds(2));
data_promise.set_value("下载完成的数据");
}
void data_processor(std::future<std::string> data_future) {
std::string data = data_future.get();
std::cout << "处理数据: " << data << std::endl;
}
int main() {
std::promise<std::string> prom;
std::future<std::string> fut = prom.get_future();
std::thread t1(downloader, std::move(prom));
std::thread t2(data_processor, std::move(fut));
t1.join();
t2.join();
return 0;
}
总结
std::promise
的作用:作为生产者线程的结果出口。std::future
的作用:作为消费者线程的结果入口。- 适用场景:需要明确分离"结果生产者"和"结果消费者"的多线程任务。
那么,如果不使用std::promise的话,两个线程间如何传递数据呢?
在 C++ 中,如果不使用 std::promise
和 std::future
,线程间传递数据可以通过其他同步机制实现。以下是几种常见方法:
1. 共享数据 + 互斥锁 (std::mutex
)
通过共享变量传递数据,使用 std::mutex
保护共享数据,防止数据竞争。
示例:
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::mutex mtx;
int shared_data = 0;
void producer() {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::lock_guard<std::mutex> lock(mtx);
shared_data = 42; // 生产者写入数据
}
void consumer() {
std::lock_guard<std::mutex> lock(mtx);
std::cout << "消费者收到数据: " << shared_data << std::endl;
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
缺点:需要手动同步,容易因锁的顺序不当导致死锁。
2. 条件变量 (std::condition_variable
)
通过条件变量通知其他线程数据已就绪,通常与互斥锁和共享变量配合使用。
示例:
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool data_ready = false;
int shared_data = 0;
void producer() {
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lock(mtx);
shared_data = 42;
data_ready = true;
}
cv.notify_one(); // 通知消费者
}
void consumer() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return data_ready; }); // 等待数据就绪
std::cout << "消费者收到数据: " << shared_data << std::endl;
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
优点:避免忙等待,效率更高。
3. 原子操作 (std::atomic
)
对简单数据类型(如 int
、bool
)使用原子变量,无需显式加锁。
示例:
cpp
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> atomic_data(0);
void producer() {
std::this_thread::sleep_for(std::chrono::seconds(1));
atomic_data.store(42); // 原子写入
}
void consumer() {
while (atomic_data.load() == 0) { // 原子读取
std::this_thread::yield(); // 避免忙等待
}
std::cout << "消费者收到数据: " << atomic_data.load() << std::endl;
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
缺点:仅适用于基本数据类型,无法处理复杂对象。
4. 消息队列(生产者-消费者模式)
使用队列作为缓冲区,生产者写入数据,消费者读取数据。需配合互斥锁和条件变量。
示例:
cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> msg_queue;
void producer() {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
{
std::lock_guard<std::mutex> lock(mtx);
msg_queue.push(i);
}
cv.notify_one(); // 通知消费者
}
}
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return !msg_queue.empty(); });
int data = msg_queue.front();
msg_queue.pop();
std::cout << "消费者收到数据: " << data << std::endl;
if (data == 4) break; // 结束条件
}
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
优点:适合处理大量数据或流式数据。
5. 回调函数(函数参数传递)
通过函数参数直接将数据传递给线程,但需注意线程的生命周期。
示例:
cpp
#include <iostream>
#include <thread>
#include <chrono>
void consumer(int data) {
std::cout << "消费者收到数据: " << data << std::endl;
}
int main() {
int data = 42;
std::thread t(consumer, data); // 通过参数传递数据
t.join();
return 0;
}
缺点:仅适用于一次性数据传递,无法异步修改。
6. 全局变量
直接通过全局变量传递数据(需谨慎,通常不推荐)。
示例:
cpp
#include <iostream>
#include <thread>
#include <chrono>
int global_data = 0;
void producer() {
std::this_thread::sleep_for(std::chrono::seconds(1));
global_data = 42;
}
void consumer() {
while (global_data == 0) {
std::this_thread::yield();
}
std::cout << "消费者收到数据: " << global_data << std::endl;
}
int main() {
std::thread t1(producer);
std::thread t2(consumer);
t1.join();
t2.join();
return 0;
}
缺点:不安全,易引发数据竞争。
总结
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
共享数据 + 互斥锁 | 简单数据,少量访问 | 灵活通用 | 需手动管理锁,易死锁 |
条件变量 | 需要等待特定条件 | 高效,避免忙等待 | 代码复杂度较高 |
原子操作 | 基本数据类型,无锁需求 | 无锁,高效 | 仅支持基本类型 |
消息队列 | 大量数据或流式处理 | 扩展性强 | 实现复杂 |
回调函数 | 一次性数据传递 | 简单直接 | 无法动态更新数据 |
全局变量 | 快速原型开发(不推荐生产环境) | 简单 | 不安全,易引发数据竞争 |
根据具体需求选择合适的方法。对于异步结果传递,std::promise
和 std::future
是最安全和简洁的方案,但在其他场景下,上述方法可能更灵活。