如何理解std::promise和std::future

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 传递给消费者线程。
  • 等待两个线程完成。

关键点

  1. 线程间结果传递

    • 生产者线程通过 promise.set_value() 设置结果。
    • 消费者线程通过 future.get() 获取结果。
  2. 所有权转移

    • std::promisestd::future 不可复制,但可通过 std::move 转移所有权。
    • 主线程将 promise 移入生产者线程,future 移入消费者线程。
  3. 同步机制

    • 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::promisestd::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)

对简单数据类型(如 intbool)使用原子变量,无需显式加锁。

示例:
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::promisestd::future 是最安全和简洁的方案,但在其他场景下,上述方法可能更灵活。

相关推荐
Bczheng19 分钟前
C++ 语法之函数和函数指针
开发语言·c++
C语言小火车13 分钟前
Redis 10大核心场景实战手册:从缓存加速到分布式锁的全面解析
c语言·开发语言·数据库·c++·redis
xiecoding.cn2 小时前
C语言和C++到底有什么关系?
c语言·开发语言·c++·c/c++·c语言入门
c7_ln2 小时前
C++输入输出流第一弹:标准输入输出流 详解(带测试代码)
c++·g++·标准输入输出
Aomnitrix3 小时前
Qt 实操记录:打造自己的“ QQ 音乐播放器”
开发语言·c++·qt·ui·音视频
wzysyrda3 小时前
CRTP奇异递归模板模式
c++
f狐0狸x3 小时前
【蓝桥杯每日一题】3.17
c语言·c++·算法·蓝桥杯·二进制枚举
Elnaij5 小时前
从C语言开始的C++编程生活(1)
c语言·c++
此刻我在家里喂猪呢5 小时前
C++ 介绍STL底层一些数据结构
c++
PingdiGuo_guo5 小时前
C++前缀和
开发语言·c++