并发编程std::promise的使用及注意点

Promise使用详解
本文通过两个示例详细介绍了C++中promise的基本用法及其注意事项,包括如何正确地在不同线程间传递并设置值,以及如何避免常见的异常情况。

 

##### 注意:正确的使用promise要必须知道它什么时候会抛出异常

enum class future_errc {
    broken_promise             = /* promise已被析构*/,
    future_already_retrieved   = /* get_future()多次 */,
    promise_already_satisfied  = /* set_value()多次 */,
    no_state                   = /* future.get()已经成功,后面又future.get()了一次 */
};


>**example:**
 

#include <iostream>
#include <memory>
#include <thread>
#include <future>
#include <mutex>
#include <chrono>
#include <string>
static std::string  future_string[] =
{ "ready",
   "timeout",
   "deferred"
};

int main()
{
    auto func = [](std::shared_ptr<std::promise<int>> ptr) {
        std::cout << "[thread]" << ptr.use_count() << std::endl;
        std::cout << "[thread] will set_value after 4s" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(4));
        if (ptr != nullptr)
        {
            ptr->set_value(20);
            std::cout << "[thread] set value done111~ " << ptr.use_count() << std::endl;
            ptr = nullptr;
            std::cout << "[thread] set value done222~ " << ptr.use_count() << std::endl;
        }
    };

    std::thread th_;

    {
        //构建promise
        std::shared_ptr<std::promise<int>> sp = std::make_shared<std::promise<int>>();
        std::future<int> future = sp->get_future();
        std::future_status status;

        //创建线程, 以智能指针方式传递
        //以普通指针传递会有特殊情况,主线程等待超时都没等到函数退出
        //就会析构promise,此后线程中调用promise->setValue()就会报错
        th_ = std::thread(func, sp);

        try
        {
            //设置10s内轮询查看当前future状态
            for (int i = 0; i < 10; ++i)
            {
                status = future.wait_for(std::chrono::seconds(1));
                std::cout << "[main] ---> current status:  " << future_string[static_cast<int>(status)] << "\t" << i << "s" << std::endl;
                if (status == std::future_status::ready)
                {
                    std::cout << "[main get the promise result:   " << future.get() << std::endl;
                    break;  /*一定要退出,只能get一次,多次就会有异常*/
                }
            }
        }
        catch (const std::exception& e)
        {
            std::cerr << "error: " << e.what() << std::endl;
        }
        
                
        std::cout << "[main]  func will exit, start delete promise_________  " << sp.use_count() << std::endl;
    }

    th_.join();
    return 0;
}

#include <iostream>
#include <future>
#include <thread>
#include <chrono>
#include <mutex>
#include <atomic>

std::mutex mtx_;
std::atomic<bool>  stop_ = false;
int memory = 0;
std::shared_ptr<std::promise<int>>  sp_po = nullptr;

void test()
{
	while (!stop_)
	{
		std::unique_lock<std::mutex> lck(mtx_);
		if (sp_po != nullptr)
		{
			try {
				sp_po->set_value(memory);
                sp_po = nullptr;
			}
			catch (std::exception& ec)
			{
				std::cout << "error " << ec.what() << std::endl;
			}
		}			
		lck.unlock();
		
		std::cout << "thread set value " << memory << std::endl;
		memory++;		
		std::this_thread::sleep_for(std::chrono::milliseconds(500));
	}
}

int main()
{	
	std::thread th(test);

	while(1)
	{		
		std::unique_lock<std::mutex> lck(mtx_);		
		sp_po.reset(new std::promise<int>);
		lck.unlock();

		std::cout << "main get value: " << sp_po->get_future().get() << std::endl;
	}


	return 0;
}

<think>首先,用户的问题是:我想理解 C++std::packaged_task 与 std::promise 的区别及使用场景。具体是:请问C++ std::packaged_task vs std::promise 区别 使用场景。 我需要根据系统级指令来回答: - 所有行内数学表达式使用 $...$ 格式。但在这个上下文中,可能没有数学表达式,主要是C++代码和概念。 - 独立公式使用 $$...$$,但这里可能不适用。 - LaTeX语法正确:确保任何代码或特殊格式正确。 - 使用中文回答:整个回答必须用中文。 - 生成相关问题:在回答结束后,添加一个相关问题部分。 - 引用段落末尾添加引用标识:如[^1]。用户提供了引用[1]到[4],我需要参考这些,但用户说“上述引用仅供参考”,所以我应该基于这些引用内容来支持我的回答,但不要直接复制。引用是: - [^1]: 关于std::packaged_task的使用场景和示例代码。 - [^2]: 关于std::packaged_task和std::promise的简介和代码案例。 - [^3]: 关于C++异步操作:std::future, std::async, std::packaged_task, std::promise。 - [^4]: 关于std::future, std::promise, std::packaged_task, std::async,包括异常处理。 回答结构: - 结构清晰,逐步解决问题。 - 保证真实可靠,基于C++知识。 内容大纲: 1. **介绍**:简要介绍std::packaged_task和std::promise。 2. **区别**:详细说明两者的区别。 - std::packaged_task:用于包装可调用对象,并提供一个future来获取结果。 - std::promise:用于在某个线程中设置值,并在其他线程中通过future获取。 3. **使用场景**: - std::packaged_task:当你想异步执行一个任务并获取结果时使用。 - std::promise:当你想在某个线程中显式设置一个值或异常时使用。 4. **代码示例**:提供简单的代码片段来说明。 5. **总结**:强调关键区别。 6. **相关问题**:生成2-3个相关问题。 使用引用:在回答中,当引用特定内容时,添加引用标识。例如,提到std::packaged_task的包装功能时,引用[^2]。 确保回答是中文。 最终输出格式: - 回答内容。 - 相关问题部分:以“§§相关问题§§”开头,然后列出问题。 现在,构建回答。</think>### C++std::packaged_task 与 std::promise 的区别及使用场景 在 C++ 并发编程中,`std::packaged_task` 和 `std::promise` 都是用于管理异步操作结果的重要工具,它们与 `std::future` 结合使用,但设计目的和使用方式有显著区别。下面我将逐步解释两者的核心概念、区别及典型使用场景,确保内容真实可靠,基于 C++11 及以上标准。回答中会引用相关资源来支持关键,并在引用段落末尾添加标识符(如[^1])。 #### 1. **核心概念介绍** - **`std::packaged_task`**: 这是一个类模板,用于包装任何可调用对象(如函数、lambda 表达式或函数对象),并将其与一个 `std::future` 关联。当你调用 `std::packaged_task` 对象时,它会执行包装的任务,并将结果(或异常)存储到关联的 future 中,以便其他线程可以获取。本质上,它提供了一种“任务封装”机制,简化了异步任务的启动和结果获取[^2]。 - 示例代码: ```cpp #include <future> #include <iostream> int add(int a, int b) { return a + b; } int main() { std::packaged_task<int(int, int)> task(add); // 包装函数 std::future<int> result = task.get_future(); // 获取关联的 future std::thread t(std::move(task), 2, 3); // 在新线程执行任务 t.join(); std::cout << "Result: " << result.get() << std::endl; // 输出: Result: 5 return 0; } ``` - **`std::promise`**: 这也是一个类模板,用于在某个线程中显式设置一个值(或异常),并通过关联的 `std::future` 在其他线程中获取。与 `std::packaged_task` 不同,`std::promise` 不直接包装任务,而是提供一个“手动控制”的机制,允许你在任意设置结果,适用于更灵活的异步场景[^2][^3]。 - 示例代码: ```cpp #include <future> #include <iostream> #include <thread> void set_value(std::promise<int> prom) { prom.set_value(42); // 在线程中设置值 } int main() { std::promise<int> prom; std::future<int> fut = prom.get_future(); // 获取关联的 future std::thread t(set_value, std::move(prom)); // 传递 promise 到线程 t.join(); std::cout << "Value: " << fut.get() << std::endl; // 输出: Value: 42 return 0; } ``` #### 2. **主要区别** 两者的核心区别在于**封装方式**和**控制粒度**: - **任务封装 vs. 值设置**: - `std::packaged_task` 专注于**封装整个任务**:它将一个可调用对象(如函数)包装起来,并自动管理结果的存储。调用任务后,结果自动传递给 future,无需手动干预[^2][^3]。 - `std::promise` 专注于**手动设置值或异常**:它不绑定具体任务,而是允许你在代码中任意位置调用 `set_value()` 或 `set_exception()` 来传递结果。这提供了更高的灵活性,但需要开发者手动管理[^3][^4]。 - **使用复杂性**: - `std::packaged_task` 更简单:适用于“执行一个任务并获取结果”的场景,减少了样板代码。例如,在任务队列中,它可以轻松提交任务并获取 future[^1]。 - `std::promise` 更底层:适用于需要精细控制的场景,比如在回调函数或事件驱动代码中设置值,或者处理异常时手动抛出异常到 future[^4]。 - **异常处理**: - 两者都支持异常传播:如果任务中抛出异常,`std::packaged_task` 会自动捕获并存储到 future 中;`std::promise` 则需要显式调用 `set_exception()`。例如,引用[^4] 中展示了 `std::async` 的异常处理,但原理类似:异常会保存在 future 中,通过 `get()` 获取时重新抛出。 - `std::packaged_task` 异常示例: ```cpp std::packaged_task<int()> task([]() { throw std::runtime_error("error"); return 0; }); std::future<int> fut = task.get_future(); std::thread t(std::move(task)); t.join(); try { fut.get(); } catch (const std::exception& e) { /* 处理异常 */ } ``` - `std::promise` 异常示例: ```cpp std::promise<int> prom; std::future<int> fut = prom.get_future(); std::thread t([&prom]() { try { throw std::runtime_error("error"); } catch (...) { prom.set_exception(std::current_exception()); } }); t.join(); try { fut.get(); } catch (const std::exception& e) { /* 处理异常 */ } ``` - **性能与开销**: - `std::packaged_task` 通常有较低的开销,因为它直接绑定任务,减少了间接调用。 - `std::promise` 可能更轻量,但需要更多手动代码,适合高频或定制化场景。 #### 3. **典型使用场景** 基于引用资源[^1][^2][^3][^4],以下是两者的常见应用场景: - **`std::packaged_task` 的使用场景**: - **任务队列系统**:当需要将多个任务提交到线程池并异步获取结果时,`std::packaged_task` 是理想选择。例如,在 GUI 应用中,后台计算任务的结果通过 future 返回到主线程[^1]。 - **简化异步调用**:如果你有一个现有函数,想在新线程中运行并获取结果,使用 `std::packaged_task` 可以避免手动管理 promise 和 future 的复杂性[^3]。 - **结合 `std::thread` 或 `std::async`**:它常作为 `std::async` 的底层实现,提供更直接的控制。 - **`std::promise` 的使用场景**: - **跨线程值传递**:当需要在不同线程间手动设置值,如回调函数或事件处理中。例如,在一个网络请求线程中,收到响应后通过 `set_value()` 通知主线程[^2][^3]。 - **异常处理与取消操作**:在复杂错误处理中,`std::promise` 允许显式设置异常或取消标志,提供更细粒度的控制。引用[^4] 中的示例展示了如何在任务失败时传播异常。 - **与第三方库集成**:当调用非 C++ 标准库(如 C 库)时,`std::promise` 可以封装回调,将结果传递回 C++ 线程。 #### 4. **总结** - **关键区别**:`std::packaged_task` 是“任务导向”的,自动封装可调用对象并管理结果;`std::promise` 是“值导向”的,提供手动设置结果的灵活性。选择时,优先用 `std::packaged_task` 简化任务执行,用 `std::promise` 处理需要显式控制的场景[^1][^3]。 - **最佳实践**:在 C++ 并发编程中,两者常与 `std::future` 和 `std::async` 结合使用。例如,`std::async` 内部可能使用 `std::packaged_task` 来实现,而 `std::promise` 则用于定制化需求。始终确保资源安全(如使用 `std::move` 避免拷贝)和异常处理[^4]。 通过合理选择工具,可以提升代码的可读性和性能。如果您有具体代码案例,我可以进一步分析优化方案。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何处惹尘埃~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值