c++ package_task
int print_sum(int a, int b) {std::cout << a << " + " << b << " = " << (a + b) << std::endl;return a + b;
}int main() {// 创建绑定后的可调用对象auto print_sum_5 = std::bind(print_sum, 5, 6);// 包装为 packaged_task//std::packaged_task<int(int)> task(print_sum_5);auto task = std::make_shared<std::packaged_task<int()>>(print_sum_5);// 获取 futureauto future = task->get_future();// 在新线程中执行// std::thread t(std::move(*task) );std::thread t([task]() {(*task)(); });t.join();// 对于 void 返回类型,future.get() 只是等待完成int c= future.get();std::cout << c << std::endl;return 0;
}
// 在新线程中执行
std::thread t(std::move(*task) );
std::thread t([task]() {(*task)(); });
这2中写法都可以运行 具体怎么解释 有什么不同
这两种线程启动方式都可以工作,但它们有不同的语义和适用场景。我来详细解释它们的区别:
1. std::thread t(std::move(*task));
工作原理:
-
解引用
shared_ptr
得到packaged_task
对象 -
使用
std::move
将对象的所有权转移到新线程 -
线程构造函数直接调用
packaged_task
的operator()
特点:
-
所有权转移:
packaged_task
被移动到新线程中,原始task
变为空 -
一次性使用:
packaged_task
只能被调用一次,移动后原对象不再可用 -
不需要lambda:更直接,但灵活性较低
潜在问题:
-
如果其他代码还想使用这个
task
会出问题(因为已被移动) -
shared_ptr
的引用计数机制在这里实际上没有被充分利用
2. std::thread t([task]() {(*task)(); });
工作原理:
-
Lambda 以值捕获方式捕获
shared_ptr
(增加引用计数) -
在新线程中解引用并调用
packaged_task
-
shared_ptr
保证packaged_task
生命周期
特点:
-
共享所有权:
shared_ptr
的引用计数确保对象存活 -
更安全:即使主线程先结束,
packaged_task
仍然有效 -
更灵活:可以在lambda中添加额外逻辑
-
符合RAII:更好地利用
shared_ptr
的生命周期管理
优势:
-
允许多个线程共享访问同一个任务(虽然通常不推荐)
-
更适合复杂场景或需要额外处理的情况
关键区别对比:
特性 | std::move(*task) 方式 | Lambda捕获 shared_ptr 方式 |
---|---|---|
所有权 | 转移所有权 | 共享所有权 |
生命周期管理 | 依赖线程生命周期 | 由 shared_ptr 引用计数管理 |
灵活性 | 较低 | 较高(可在lambda中添加逻辑) |
线程安全 | 一般 | 更好 |
典型使用场景 | 简单的一次性任务 | 需要更复杂控制的场景 |
packaged_task 状态 | 移动后原对象无效 | 原 shared_ptr 仍可使用 |
为什么两种方式都能工作?
在你的特定例子中:
-
std::bind
已经绑定了所有参数,创建的是无参可调用对象 -
packaged_task
只需要被调用一次 -
主线程会等待(
t.join()
)所以生命周期问题不会显现
推荐选择:
-
简单场景:使用
std::move(*task)
更简洁 -
复杂/生产代码:推荐lambda方式,因为:
-
更安全(明确的生命周期管理)
-
更灵活(方便添加异常处理等)
-
更符合现代C++的惯用法
-
代码示例对比:
// 方式1:移动语义
auto task = std::make_shared<std::packaged_task<int()>>(std::bind(print_sum,5,6));
auto future = task->get_future();
std::thread t1(std::move(*task)); // task现在为空
t1.join();// 方式2:lambda捕获
auto task = std::make_shared<std::packaged_task<int()>>(std::bind(print_sum,5,6));
auto future = task->get_future();
std::thread t2([task]{ (*task)(); }); // task仍有效
t2.join();
在大多数现代C++代码中,第二种方式(lambda捕获)更受推荐,因为它提供了更好的安全性和灵活性,特别是当与shared_ptr
配合使用时。第一种方式虽然也能工作,但在复杂的异步编程场景中可能不够安全。