std :: async参数的生命周期是多少? [英] What is the lifetime of the arguments of std::async?

查看:122
本文介绍了std :: async参数的生命周期是多少?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

似乎通过 std :: async 执行的函数的参数共享未来的生命周期:

It appears that arguments of a function executed via std::async share the lifetime of the future:

#include <iostream>
#include <future>
#include <thread>

struct S
{
    S() {
        std::cout << "S() " << (uintptr_t)this << std::endl;
    }

    S(S&& s) {
        std::cout << "S(&&) " << (uintptr_t)this << std::endl;
    }

    S(const S& s) = delete;

    ~S() {
        std::cout << "~S() " << (uintptr_t)this << std::endl;
    }
};

int main()
{
    {
        std::cout << "enter scope" << std::endl;
        auto func = [](S&& s) {
            std::cout << "func " << (uintptr_t)&s << std::endl;
            auto x = S();
        };
        S s;
        auto fut = std::async(std::launch::async, func, std::move(s));
        std::cout << "wait" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(5));
        fut.get();
        std::cout << "exit scope" << std::endl;
    }
    return 0;
}

结果是:

    enter scope
  ++S() 138054661364        main's variable
  | S(&&) 138054661108 ++   std::async's internal copy
+--+S(&&) 138054659668  |   std::async's internal copy
| | S(&&) 138054922824 +--+ func's argument
+--+~S() 138054659668   | |
  | ~S() 138054661108  ++ |
  | func 138054922824     |
  | S() 138057733700   +  |  local variable
  | ~S() 138057733700  +  |
  | wait                  |
  | exit scope            |
  | ~S() 138054922824  +--+
  ++~S() 138054661364

它看起来基础实现(MSVS 2015 U3)在地址 138054922824 处创建了参数的最终版本,但是直到将来销毁之前,它都不会销毁它。

It looks like the underlying implementation (MSVS 2015 U3) creates the final version of the argument at the address 138054922824, but does not destroy it until future is destroyed.

感觉这违反了RAII的承诺,因为函数实现可能会在退出时调用的参数的析构函数上进行中继。

It feels like this breaks the RAII promise as the function implementation may relay on destructors of the arguments being called upon exit.

这是错误还是传递给 std :: async 的参数的确切寿命是未知的?标准对此有何说法?

Is this a bug or the exact lifetime of the arguments passed to std::async is unknown? What does the standard say about this?

推荐答案

在我之前的评论中加上实际答案……

Following up on my previous comment with an actual answer…

我在libstdc ++中遇到了相同的行为。我没有想到这种行为,并且在我的代码中导致了死锁错误(遗憾的是,由于等待超时,这只会导致程序终止的延迟)。在这种情况下,任务完成执行后才销毁任务对象(我的意思是功能对象 f ),但是只是销毁了未来。 ,很可能实现会以相同的方式对待任务对象和任何参数。

I have encountered the same behavior with libstdc++. I did not expect this behavior, and it resulted in a deadlock bug in my code (thankfully, due to a wait timeout, this only caused a delay in program termination). In this case, it was the task object (by which I mean the function object f) that was not destroyed after the task finished execution, only on destruction of the future, however, it is likely that the task object and any arguments are treated in the same manner by the implementation.

std :: async [futures.async] 中已标准化


(3.1)如果在策略中设置了 launch ::: async ,调用 INVOKE(DECAY_COPY(std :: forward< F>(f)),DECAY_COPY(std¶:: forward< Args>(args))...)([func.require],[thread.thread.constr])好像在一个新的执行线程中,该执行线程由调用 DECAY_COPY()的线程对象表示在名为 async 的线程中进行评估。任何返回值都作为结果存储在共享状态中。从 INVOKE(DECAY_COPY(std ::: forward< F>(f)),DECAY_COPY(std ::: forward< Args>(args))...)的执行传播的任何异常作为例外结果存储在共享状态中。线程对象存储在共享状态下,并且会影响引用该状态的所有异步返回对象的行为。

(3.1) If launch​::​async is set in policy, calls INVOKE(DECAY_­COPY(std​::​forward<F>(f)), DECAY_­COPY(std​::​forward<Args>(args))...) ([func.require], [thread.thread.constr]) as if in a new thread of execution represented by a thread object with the calls to DECAY_­COPY() being evaluated in the thread that called async. Any return value is stored as the result in the shared state. Any exception propagated from the execution of INVOKE(DECAY_­COPY(std​::​forward<F>(f)), DECAY_­COPY(std​::​forward<Args>(args))...) is stored as the exceptional result in the shared state. The thread object is stored in the shared state and affects the behavior of any asynchronous return objects that reference that state.

使用 DECAY_COPY 而不命名结果,并在 INVOKE 表达式中使用,强烈建议使用在以下位置销毁的临时对象包含 INVOKE 的完整表达式的结尾,该表达式发生在新的执行线程上。但是,这还不足以得出以下结论:参数(的副本)没有使函数调用的生存时间超过清理它们的处理时间(或任何合理的延迟)。其理由如下:基本上,标准要求在执行线程完成时销毁对象。但是,该标准不要求在执行等待调用之前或在销毁未来之前先完成执行线程:

The wording, by using DECAY_COPY without naming the results and inside an INVOKE expression, does strongly suggest the use of temporary objects that are destroyed at the end of the full expression containing the INVOKE, which happens on the new thread of execution. However, this is not enough to conclude that the (copies of the) arguments, do not outlive the function call by more than the processing time it takes to clean them up (or any "reasonable delay"). The reasoning for it goes like this: Basically the standard requires that the objects are destroyed when the thread of execution completes. However, the standard does not require that the thread of execution completes before a waiting call is made or the future is destroyed:


如果实现选择了 launch ::: async 策略,

(5.3)$ b共享此异步调用所创建的共享状态的异步返回对象上的等待函数的$ ba调用将阻塞,直到相关联的线程已完成(好像已加入)或超时([thread.thread.member])为止;

(5.3) a call to a waiting function on an asynchronous return object that shares the shared state created by this async call shall block until the associated thread has completed, as if joined, or else time out ([thread.thread.member]);

因此,等待调用可能使线程完成,然后才等待其完成。在假设规则下,如果代码仅表现出这种行为,则它们实际上可能会做得更糟,例如,将任务和/或参数公然地存储在共享状态下(请注意)。

So, the waiting call could cause the thread to complete and only then wait on its completion. Under the as-if rule, the code could actually do worse things if they only appear to have this behavior, such as blatantly storing the task and/or arguments in the shared state (with the caveat to immediately follow). This does appear to be a loophole, IMO.

libstdc ++的行为使得即使是无条件的 wait()不足以导致任务和参数被破坏–仅 get()或对未来意志的破坏。如果调用 share(),仅销毁 shared_future 的所有副本就足以导致销毁。这似乎确实是一个错误,因为 wait()在(5.3)中的 waiting function一词中肯定包含,并且不能超时。除此之外,该行为似乎是未指定 –不管是不是疏忽。

The behavior of libstdc++ is such that even an unconditional wait() is not enough to cause task and arguments to be destroyed – only a get() or destruction of the future will. If share() is called, only destruction of all copies of the shared_future is sufficient to cause the destruction. This appears to be a bug indeed, as wait() is certainly covered by the term "waiting function" in (5.3), and cannot time out. Other than that, the behavior seems to be unspecified – whether that's an oversight or not.

我的猜测是为什么实现似乎将处于共享状态的对象的实现比标准的建议要容易得多(在目标线程上创建临时副本,并与 std :: async )。

My guess as to why implementations seem to put the objects in the shared state is that this is much easier to implement than what the standard would literally suggest (making temporary copies on the target thread, synchronous with the call of std::async).

似乎应该提出LWG问题。不幸的是,对此的任何修复都可能破坏多个实现的ABI,因此,即使已批准更改,也可能需要数年才能在部署中可靠地解决该行为。

It seems like an LWG issue should be brought up about this. Unfortunately, any fix for this is likely to break the ABI of multiple implementations, and it may therefore take years until the behavior is reliably fixed in deployments, even if the change is approved.

我个人得出一个不幸的结论,即 std :: async 有太多的设计和实现问题,以至于在不平凡的应用程序中几乎没有用。我的代码中的上述错误已通过我自己的(依赖跟踪)线程池类替换了 std :: async 的有问题的使用而得以解决,这会破坏任务任务完成执行后尽快包括所有捕获的对象。 (它只是从队列中弹出任务信息对象,该对象包含类型删除的任务,promise等)。

Personally, I have come to the unfortunate conclusion that std::async has so many design and implementation issues that it is next to useless in a non-trivial application. The aforementioned bug in my code has been resolved by me replacing the offending use of std::async by uses of my own (dependency tracking) thread pool class, which destroys the task including all captured objects ASAP after the task finishes execution. (It simply pops the task info object, which contains the type-erased task, the promise and so on, from the queue.)

更新:应该注意的是, libstdc ++的 std :: packaged_task 具有相同的行为,该任务似乎已移至共享状态,并且在 std :: packaged_task时不会被破坏是,只要 get()或任何将来的析构函数都处于等待状态。

UPDATE: It should be noted that libstdc++'s std::packaged_task has the same behavior, the task appears to be moved into the shared state and will not be destroyed when the std::packaged_task is, as long as get() or any future destructors are pending.

这篇关于std :: async参数的生命周期是多少?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆