来自原始评论的内容。
有的。这是简化的。我在实际代码中使用 futures 和一个特殊的容器。它旨在用于多线程环境中。
这被称为隐藏主题。
如果您正在存储可调用对象以在其他线程中调用,在另一个线程中,您需要签名 void()
。在 此 线程中,您需要填充一个 std::future
。
至于绑定参数,虽然很多 std
函数会为您执行此操作,但我发现最好要求具有预绑定参数的可调用对象。他们可以在外部使用 std::bind
、lambda 或任何其他他们选择的方法来完成这项任务。
所以就是这样。
template<class Func,
class R = std::decay_t<std::result_of_t<Func const&()>>
>
std::future< R >
addTask( Func&& func ) {
auto task = std::packaged_task<R()>(std::forward<Func>(func));
auto ret = task.get_future();
container.push_back( std::packaged_task<void()>( std::move(task) ) );
return ret;
}
std::deque< std::packaged_task<void()> > container;
加入一些互斥锁并摇晃即可。
在这里,我使用 std::packaged_task<void()>
作为预先编写的移动封装容器,用于任何具有该签名的内容。我们不使用它可以产生的 future
,这是一种浪费,但它比编写自己的移动仅拥有一次的函数对象更短。
我个人只是编写了一个轻量级的移动封装类似于 std::function<void()>
,而不是使用 std::packaged_task<void()>
,但这可能是不明智的。
addTask
返回的 future 在调用 packaged_task<R()>
时得到满足,当调用 packaged_task<void()>
时调用它(可能在另一个线程中)。
结构之外,调用者可以提供任何零参数可调用对象。
99 次中有 100 次,简单的 [some_arg]{ some_code; }
或甚至 []{ some_code; }
即可工作。在复杂情况下,它们可以使用 std::bind
或 C++14 中更复杂的 lambda 来处理。
将参数的存储放入 addTask
中混合了线程任务队列的责任和处理参数的责任。
实际上,我会单独编写一个线程安全的队列,然后让线程池使用它:
template<class T>
struct thread_safe_queue;
struct thread_pool {
thread_safe_queue< std::packaged_task<void()> > queue;
};
在C++17中,替代bind的函数如下所示:
[
func = std::forward<Func>(func),
args = std::make_tuple( std::forward<Args>(args)... )
]() mutable {
std::apply( func, std::move(args) );
}
在C++14中,你可以很容易地编写
notstd :: apply
。 如果你需要有效地移动参数,你需要使用std绑定或在C++11中手动创建函数对象。
我会主张将参数绑定强烈放在使用线程池的代码领域内是最好的选择。
这也允许线程池执行像传递任务可选的额外参数,比如“取消令牌”之类的操作。
std::bind
就是旧的std::bind2nd
的现代等价物。std :: bind
不再很酷了吗? 说真的,好问题。 - BathshebaT t = func();
编译的函数对象。 - StoryTeller - Unslander Monicabind
looks fine here. beats writingcontainer.emplace_back([func, args...](){ return func(args...); });
- NathanOliver