微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

wait_for_any/when_any/WaitAny/WhenAny:传递零期货/任务时的正确行为是什么?

如何解决wait_for_any/when_any/WaitAny/WhenAny:传递零期货/任务时的正确行为是什么?

when_any 传递零期货时,有 4 个设计选项可供选择,不幸的是,所有这些都有意义。
直到现在我能够

  1. 总结每个设计选项的一些弱论点;
  2. 列出一些实现以及他们选择的设计选项。

设计选项 1:when_any(zero future<T>s) 应该返回一个永远阻塞的未来。

一个原因是定义的简单性和统一性。

如果 when_any(zero futures) 返回一个永远阻塞的未来,我们得到:

wait_all 阻塞,有些未完成,直到全部完成;
wait_any 在某些完成时解除阻塞,如果所有未完成则不阻塞;

如果 when_any(zero futures) 返回一个立即准备好的未来,我们得到:

wait_all 阻塞,有些未完成,直到全部完成;
wait_any 在某些完成时解除阻塞,不是在所有未完成时解除阻塞,而是在有零个期货时解除阻塞;

第二个原因是 when_any一个结合和交换的二元运算,所以对于 when_any 的可变参数版本我们想要返回 when_any 运算的单位元素(这是一个永远阻止的未来)。在您可以定义自己的二元运算符(可能是 C++ 将来会这样做)或支持 std::accumulate 算法的语言中,您迟早仍会遇到此标识元素问题。

when_all 就像 operator&&,在参数包扩展中,空包扩展为 trueoperator&&true 就像一个立即出现的未来准备好了。
when_any 就像 operator||,在参数包扩展中,空包扩展到 falseoperator||false 就像一个永远不会准备好的未来。

(我们需要标识元素的其他地方是:

  • boost::thread::null_mutexstd::scoped_lockstd::scoped_lock 就像一个关联和交换的二元运算,消耗较小的锁并产生较大的锁),
  • std::monostatestd::variantstd::variant 就像一个结合和交换的二元运算,消耗较小的联合并产生较大的联合),
  • 在正则表达式中将空设置为 operator|(如果您编写将 NFA 转换为正则表达式的程序,则可能会发生具有零个子节点的 operator| 节点),
  • 正则表达式中 operator concat 的空字符串,
  • ...

)

我们应该如何处理分歧?

一个程序可能:

  • 产生价值;
  • 产生错误(程序的状态超出了评估函数的范围);
  • 发散(对于每个状态X,都存在一个状态Y:X ---[评估函数]--> Y);

发散不是值,发散不是错误,发散就是发散。
有些程序会发散(永不终止),例如操作系统、协议栈、数据库服务和 Web 服务器。
有办法处理分歧。在 C# 中,我们有取消功能和进度报告功能。在 C++ 中,我们可以中断执行代理 (boost.thread) 或销毁执行代理 (boost.context,boost.fiber)。我们可以使用线程安全的队列或通道来不断地向/从参与者发送/接收值/错误

分歧用例①:

程序员使用 library1 在不可靠的网络上查询一些不可靠的 Web 服务。 library1 永远重试,因为网络不可靠。 library1 本身不应在某些超时到期时将异常存储在共享状态,因为:

  1. 应用层程序员可能想要使用不同的取消机制:
  • 超时到期时,或
  • 用户点击按钮时,或
  • 用户点击按钮开始超时以及超时到期时;
  1. 应用层程序员可能希望在取消时做不同的事情:
  • 提供一个认值,或
  • 提供例外,或
  • 有些程序员不在最上层,所以不应该附加取消机制;

无论如何,程序员必须使用 when_any 将可能永久阻塞的未来与他自己的我的取消/回退机制未来合并,以获得更大的未来,而更大的未来现在不会发散。
(假设 when_any(several future<T>...) 返回 future<T>,因此我们不必在未来树中的每个中间 when_any 节点上编写样板代码。)
(需要做一些修改:(1)when_any 返回的更大的 Future 应该在第一个子 Future 准备好时销毁其他子 Future;(2)library1 应该使用 promise 对象来检查 if(shared_state.reference_count == 1) 并获得知道消费者已经放弃了未来(即操作被取消),并退出该循环;)

发散用例②:

程序员使用 library2 在不可靠的网络上查询一些不可靠的 Web 服务。 library2 重试 n 次,然后通过在 shared_state(shared_state.diverge = truestared_state.state = state_t::diverge)中设置一个位来永久阻塞,这不是物理上的,而是逻辑上的。程序员使用 when_any 合并来自 library2 的未来和 my-cancelation/fallback-machanism 未来。第一个准备好的子未来指示结果。假设一个失败的子未来准备好有异常而不是永远阻塞,那么它回答更大的未来,而不是稍后准备好的成功的子未来,这是不希望的。
(假设 when_any(several future<T>...) 返回 future<T>,因此我们不必在未来树中的每个中间 when_any 节点上编写样板代码。)

发散用例③:

在测试网络代码时,使用从未准备好代表网络状况非常差的客户端的未来,使用立即准备好代表网络状况非常好的客户端的未来,使用具有各种超时代表介于两者之间的客户端。
(需要做一些修改:(1)添加make_diverging_future;(2)添加make_timeout_ready_future;)

设计选项 2:when_any(zero future<T>s) 应该返回一个包含异常的未来。

c++ - std::when_any() 的非轮询实现 https://codereview.stackexchange.com/questions/176168/non-polling-implementation-of-stdwhen-any

并发 TS 的 when_any 在使用零参数调用时从哲学上不正确地返回一个就绪的未来。我的版本没有特别处理这种情况,因此自然行为会失败:内部 promise 在 0 提供的期货中的任何一个准备就绪之前被销毁,因此 when_any(/*zero args*/) 返回一个准备好的未来其 get() 将抛出 broken_promise

我认为这是一个“早失败,大声失败”的案例。由于他将发散视为错误,因此在上述用例中会出现问题。

设计选项 3:when_any(zero future<T>s) 应该返回一个包含 ??? 值的未来。

设计选项 4:when_any(zero future<T>s) 应该被禁止

标准和库使用最后 3 个设计选项。我会在下面猜测他们的动机。

以下是一些实现及其在 *_all 和 *_any 上的行为:
cpu 密集型程序的函数:(如果您在阅读表格时遇到问题,请转到编辑模式

哪里 功能 传递零任务时的行为
boost.thread *_all void wait_for_all(...) 返回void
boost.thread *_any iterator wait_for_any(...) 返回结束迭代器
boost.fiber *_all void wait_all_simple(...) 在编译时拒绝
vector<R> wait_all_values(...) 在编译时拒绝
vector<R> wait_all_until_error(...) 在编译时拒绝
vector<R> wait_all_collect_errors(...) 在编译时拒绝
R wait_all_members(...) 返回值 R
boost.fiber *_any void wait_first_simple(...) 返回void
R wait_first_value(...) 在编译时拒绝
R wait_first_outcome(...) 在编译时拒绝
R wait_first_success(...) 在编译时拒绝
variant<R> wait_first_value_het(...) 在编译时拒绝
System.Threading.Tasks *_all void Task.WaitAll(...) 返回void
System.Threading.Tasks *_any int Task.WaitAny(...) 返回-1

IO 绑定程序的函数

的未来
哪里 功能 传递零任务时的行为
std::experimental *_all future<sequence<future<T>>> when_all(...) 返回一个存储空序列的未来
std::experimental *_any future<...> when_any(...) 返回存储 { size index = -1,sequence<future<T>> sequence = empty sequence }
boost.thread *_all future<sequence<future<T>>> when_all(...) 返回一个存储空序列的未来
boost.thread *_any future<sequence<future<T>>> when_any(...) 返回一个存储空序列的未来
System.Threading.Tasks *_all Task<TResult[]> Task.WhenAll(...) 返回一个存储空序列的未来
System.Threading.Tasks *_any Task<Task<TResult>> Task.WhenAny(...) 在运行时拒绝(抛出 ArgumentException

System.Threading.Tasks.WaitAny(...) 接受零期货,但 System.Threading.Tasks.WhenAny(...) 在运行时拒绝。)

我们不应该允许 when_any(zero tasks) 返回一个永远阻塞的未来的原因可能是实用性。如果我们允许这样做,我们在future 的接口中打开一个漏洞,说每个future 都可能发生分歧,所以每个应用层程序员都必须使用when_any 将future 与my-cancelation/fallback 合并-如果他缺乏进一步的信息,就可以获得更大的永不阻塞的未来,这很乏味。如果我们不允许这样做,我们将保护那些没有详细记录所有接口的团队(让我打个比方:假设您在一家 C++ 公司,其中库函数接收和返回潜在的 nullptr 指针而不是 {{ 1}} 和 optional<reference_wrapper<T>>,在没有更多信息或文档的情况下,您必须使用 reference_wrapper<T> 保护每个成员访问表达式,这很乏味;同样,对于 Future,我们必须在任何地方都执行 if(p))。所以我们最好让接口和可能性尽可能小。

(向 Alan Birtles 道歉:我很抱歉那天我在列出的实现上犯了一个错误:boost.fiber 的 wait_any 函数除了第一个禁止零期货,还有一个单独的实现会返回一个未来存储broken_promise (https://codereview.stackexchange.com/questions/176168/non-polling-implementation-of-stdwhen-any) 所以我试图在一个新问题中总结这些。)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?