std::future 和 std::promise

std::future
用途:std::future 用于获取异步操作的结果。它充当一个占位符,等待异步操作的结果可用。
使用场景:通常由 std::async、std::promise::get_future() 或 std::packaged_task::get_future() 返回。
只可移动:std::future 是 只可移动 的,意味着它不能被复制,只能被移动。这限制了结果的获取只能由一个线程完成。
所有权:拥有 std::future 的线程可以调用 .get() 来获取结果,一旦调用了 .get(),结果就会被消耗掉(此时 future 失效)。
常用方法:
get():等待并返回结果(如果异步操作中出现异常,会抛出异常)。
valid():检查 future 是否包含有效的结果。
wait():阻塞当前线程,直到结果可用。
wait_for()、wait_until():等待指定的时间段或直到某个时间点。
std::shared_future
用途:std::shared_future 允许多个线程访问异步操作的结果。它是 std::future 的共享版本。
使用场景:可以通过 std::future::share() 从 std::future 创建 std::shared_future,这样多个线程可以共享同一个 shared_future 的副本。
可复制:与 std::future 不同,std::shared_future 是 可复制 的,意味着多个线程可以各自持有该 shared_future 的副本。
多次访问:多个线程可以调用 .get() 来获取结果,每次调用都会返回相同的结果(不会像 std::future 那样在第一次获取后失效)。
常用方法:
get():可以被多次调用,每次返回相同的异步操作结果。
valid()、wait()、wait_for()、wait_until():与 std::future 的方法相同。

std::future示例:
#include <iostream>
#include <future>
#include <chrono>

int calculateSquare(int x) {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
    return x * x;
}

int main() {
    // 使用 std::async 异步执行 calculateSquare 函数
    std::future<int> result = std::async(std::launch::async, calculateSquare, 5);

    // 主线程可以继续做其他工作
    std::cout << "Doing some work while waiting for the result...\n";

    // 获取异步结果
    int square = result.get(); // 阻塞,直到结果可用
    std::cout << "Square of 5 is: " << square << std::endl;

    return 0;
}

std::shared_future 示例
#include <iostream>
#include <future>
#include <thread>
#include <chrono>

int calculateSquare(int x) {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
    return x * x;
}

void printResult(std::shared_future<int> sharedResult) {
    // 获取共享的异步结果
    int square = sharedResult.get();
    std::cout << "Result in thread: " << square << std::endl;
}

int main() {
    // 使用 std::async 异步执行 calculateSquare 函数
    std::future<int> result = std::async(std::launch::async, calculateSquare, 5);

    // 将 std::future 转换为 std::shared_future
    std::shared_future<int> sharedResult = result.share();

    // 主线程可以继续做其他工作
    std::cout << "Doing some work while waiting for the result...\n";

    // 启动多个线程共享异步结果
    std::thread t1(printResult, sharedResult);
    std::thread t2(printResult, sharedResult);

    // 主线程也可以获取结果
    std::cout << "Result in main: " << sharedResult.get() << std::endl;

    // 等待线程结束
    t1.join();
    t2.join();

    return 0;
}

问题一
为什么需要std::future呢,直接写一个全局变量保存线程生成的值,这样是否可以?
直接使用全局变量保存线程生成的值虽然在某些情况下可以工作,但与 std::future 相比,这种方法有以下几个问题和不足:
1.线程同步和竞争条件问题
2. 结果的生命周期管理
3. 避免轮询和主动等待
4. 简化的错误处理
5. 可扩展性与线程安全
使用全局变量(不推荐):
#include <iostream>
#include <thread>
#include <atomic>

std::atomic<bool> result_ready(false);  // 标志位
int result;

void calculate() {
    result = 42;
    result_ready = true;  // 标记结果已准备好
}

int main() {
    std::thread t(calculate);

    // 主线程轮询等待结果
    while (!result_ready) {
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    
    std::cout << "Result: " << result << std::endl;
    t.join();
    return 0;
}
这个方法不仅需要使用 std::atomic 来避免竞争条件,还需要轮询,效率较低。

std::promise
std::promise 是一个写入端,用于在线程中设置异步操作的结果。

用途:std::promise 的主要作用是提供一种手段,将某个结果或状态从一个线程传递到另一个线程。
设置值:可以通过 set_value() 来设置结果,或者通过 set_exception() 来传递异常。
获取 std::future:std::promise 可以通过 get_future() 方法生成一个与之关联的 std::future,用于读取结果。

std::promise 和 std::future 之间的关系
std::promise 是生产者,用于在线程中生成并提供数据。
std::future 是消费者,用于在线程中等待并接收数据。
它们的组合为多线程间的通信提供了有效的方式,一个线程可以通过 std::promise 设置值或异常,另一个线程则通过 std::future 获取该值或处理异常。

具体例子
假设我们有两个线程,一个线程计算结果并通过 std::promise 传递给另一个线程:
#include <iostream>
#include <future>
#include <thread>

void calculateSquare(std::promise<int>& prom, int x) {
    // 模拟耗时操作
    std::this_thread::sleep_for(std::chrono::seconds(2));
    int result = x * x;
    prom.set_value(result);  // 将结果传递给 promise
}

int main() {
    std::promise<int> prom;              // 创建 promise
    std::future<int> fut = prom.get_future();  // 获取与 promise 关联的 future

    std::thread t(calculateSquare, std::ref(prom), 10);  // 在线程中执行任务

    std::cout << "Waiting for result...\n";

    // 主线程等待结果
    int square = fut.get();  // 阻塞直到结果可用
    std::cout << "Square of 10 is: " << square << std::endl;

    t.join();  // 等待线程结束
    return 0;
}

异常处理
std::promise 还可以通过 set_exception() 向 std::future 传递异常。当调用 get() 时,异常会被捕获并抛出。
#include <iostream>
#include <future>
#include <thread>
#include <exception>

void throwException(std::promise<int>& prom) {
    try {
        throw std::runtime_error("An error occurred!");
    } catch (...) {
        prom.set_exception(std::current_exception());  // 将异常传递给 promise
    }
}

int main() {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();

    std::thread t(throwException, std::ref(prom));  // 在线程中执行任务

    try {
        int result = fut.get();  // 获取结果,但由于异常会抛出异常
    } catch (const std::exception& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }

    t.join();
    return 0;
}

future只能和std::async配合使用吗?
std::future 的确常常与 std::async 一起使用,但它不仅限于这种组合。std::future 也可以与 std::promise 配合使用,或与其他线程创建方法(如 std::thread)结合使用。
1. std::future 和 std::async
#include <iostream>
#include <future>
#include <chrono>

int calculateSquare(int x) {
    std::this_thread::sleep_for(std::chrono::seconds(2));  // 模拟耗时计算
    return x * x;
}

int main() {
    std::future<int> fut = std::async(std::launch::async, calculateSquare, 10);
    std::cout << "Waiting for the result..." << std::endl;

    int result = fut.get();  // 阻塞等待计算结果
    std::cout << "Result: " << result << std::endl;

    return 0;
}

std::future 和 std::promise
#include <iostream>
#include <thread>
#include <future>

void calculateSquare(std::promise<int> prom, int x) {
    // 模拟耗时操作
    std::this_thread::sleep_for(std::chrono::seconds(1));
    prom.set_value(x * x);  // 设置结果
}

int main() {
    std::promise<int> prom;  // 创建 promise
    std::future<int> fut = prom.get_future();  // 从 promise 获取 future

    std::thread t(calculateSquare, std::move(prom), 10);  // 传递 promise

    int result = fut.get();  // 等待结果
    std::cout << "Result: " << result << std::endl;

    t.join();  // 确保线程结束
    return 0;
}

上面为什么不用std::ref, 而是用std::move?
在使用 std::promise 和 std::future 时,选择使用 std::move 而不是 std::ref 的原因主要与 std::promise 的所有权和生命周期管理有关

std::move(prom) 后,在子线程函数内设置了值并且子线程先结束了,后面主线程在调用获取值会不会应该prom对象不存在了而出问题呢?
1.std::move 的效果
使用 std::move(prom) 之后,prom 对象的所有权转移给了子线程,意味着:
在子线程中可以安全地调用 prom.set_value(...) 或 prom.set_exception(...) 来设置结果。
主线程中的 prom 变量将不再有效,因为它的状态已经被移动。

2. 子线程结束
在子线程中设置值后,子线程可以安全结束。此时,如果主线程调用 fut.get(),并不会因为 prom 对象已不存在而导致问题,因为 std::future 维护了对 std::promise 的内部状态。

3. std::future 的状态管理
std::future 实际上是持有了 std::promise 的状态(即结果或异常),而不是 std::promise 本身。因此,即使 std::promise 对象在子线程中被移动,std::future 仍然能够安全地获取设置的值。

4. 代码示例
以下是一个完整的示例,展示了在子线程中设置值后,主线程如何安全地调用 fut.get():
#include <iostream>
#include <thread>
#include <future>
#include <chrono>

void calculateSquare(std::promise<int> prom, int x) {
    // 模拟耗时计算
    std::this_thread::sleep_for(std::chrono::seconds(1));
    prom.set_value(x * x);  // 设置结果
    // 子线程结束
}

int main() {
    std::promise<int> prom;                    // 创建 promise
    std::future<int> fut = prom.get_future();  // 从 promise 获取 future

    std::thread t(calculateSquare, std::move(prom), 10);  // 转移所有权

    // 主线程可以继续做其他工作
    std::cout << "Main thread is doing other work..." << std::endl;
    t.join();  // 等待子线程结束

    int result = fut.get();  // 阻塞等待结果
    std::cout << "Result: " << result << std::endl;

    return 0;
}

std::promise 和 std::future 之间的状态管理和数据获取是 C++11 标准库线程支持的一部分,背后的实现设计非常巧妙。尽管标准库的具体实现可能会有所不同(如在不同的编译器或平台上),下面是一般的实现原理和逻辑。

1. 内部数据结构
std::promise 和 std::future 通常有一个共享的内部状态。这个状态可能是一个结构体或类,包含以下内容:

结果值:用来存储异步计算的结果。
异常:用来存储在计算过程中发生的异常(如果有的话)。
状态标志:指示结果是否已设置,可能的状态包括未设置、已设置(成功)、已设置(异常)。
2. 关联机制
当你创建一个 std::promise 对象时,它通常会有一个与之关联的内部状态,这个状态通过指针或引用的形式与 std::future 关联。
当你调用 std::promise::set_value() 或 std::promise::set_exception() 时,实际是设置了这个共享状态。
std::future 在创建时会接收一个指向这个共享状态的引用或指针,这样它可以访问相同的数据。
3. 实现步骤
具体实现步骤可能如下:

创建状态:

在 std::promise 的构造函数中创建一个状态对象,并保存其指针。
设置值或异常:

当调用 set_value() 或 set_exception() 时,std::promise 会更新这个共享状态。
获取值:

当调用 std::future::get() 时,std::future 会检查共享状态:
如果状态表示值已设置,则返回该值。
如果状态表示异常,则抛出异常。
如果值尚未设置,get() 将阻塞,直到值被设置。
4. 线程安全
为了保证线程安全,std::promise 和 std::future 的实现通常会使用互斥锁(mutex)来保护共享状态。这确保了多个线程在访问同一个状态时不会出现数据竞争。

5. 示例伪代码
以下是一个简化的伪代码示例,展示了这种内部实现的基本逻辑:
class SharedState {
public:
    std::optional<int> result;  // 存储结果
    std::exception_ptr exception; // 存储异常
    std::mutex mtx;              // 互斥锁
    std::condition_variable cv;   // 条件变量
    bool ready = false;           // 状态标志

    // 设置值
    void set_value(int val) {
        std::lock_guard<std::mutex> lock(mtx);
        result = val;
        ready = true;
        cv.notify_all(); // 通知等待的线程
    }

    // 设置异常
    void set_exception(std::exception_ptr e) {
        std::lock_guard<std::mutex> lock(mtx);
        exception = e;
        ready = true;
        cv.notify_all(); // 通知等待的线程
    }
};

class promise {
    std::shared_ptr<SharedState> state; // 共享状态

public:
    promise() : state(std::make_shared<SharedState>()) {}

    future get_future() {
        return future(state); // 返回与状态关联的 future
    }

    void set_value(int val) {
        state->set_value(val);
    }

    void set_exception(std::exception_ptr e) {
        state->set_exception(e);
    }
};

class future {
    std::shared_ptr<SharedState> state; // 共享状态

public:
    future(std::shared_ptr<SharedState> s) : state(s) {}

    int get() {
        std::unique_lock<std::mutex> lock(state->mtx);
        state->cv.wait(lock, [this]() { return state->ready; }); // 等待值准备好
        if (state->exception) {
            std::rethrow_exception(state->exception); // 抛出异常
        }
        return *state->result; // 返回结果
    }
};
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值