async总是在C ++中使用另一个线程/核心/进程吗?

时间:2017-03-22 12:18:57

标签: c++ multithreading asynchronous concurrency

据我所知async在另一个线程/进程/核心中执行一个函数并且不阻塞主线程,但总是这样吗?

我有以下代码:

async(launch::async,[]()
{
    Sleep(1000);
    puts("async");
});
puts("main");

它打印async main,这是否意味着主线程等到async结束?

如果我改为以下:

auto f = async(launch::async,[]() // add "auto f = "
{
    Sleep(1000);
    puts("async");
});
puts("main");

打印main async。这使得看起来主要不等待async完成。

4 个答案:

答案 0 :(得分:27)

  

我知道async在另一个线程/进程/核心中执行一个函数并且不阻塞主线程,但是它总是会发生吗?

仅当std::async作为第一个参数传递时,

std::launch::async才能保证在单独的线程上执行:

  
      
  • std::launch::async:启动新线程以异步执行任务
  •   
  • std::launch::deferred第一次请求结果时,在调用线程上执行任务(延迟评估)
  •   

默认启动政策std::launch::async | std::launch::deferred

std::async返回std::futurestd::future's destructor仅在未来从std::async返回时才会阻止:

  

这些操作不会阻止共享状态变为就绪,除非它可能阻塞以下所有条件都是真的:共享状态是通过调用std :: async创建的,共享状态尚未准备好,这是对共享状态的最后一次引用

  • 在您的第一个代码段中,您创建一个右值表达式 会立即销毁 - 因此"async"将在"main"之前打印}。

    1. 创建异步匿名函数并开始执行。

    2. 异步匿名函数被破坏。

      • main执行被阻止,直到功能完成。

      • "async"已打印。

    3. main执行恢复。

      • "main"已打印。
  • 在第二个代码段中,您创建一个左值表达式,其生命周期绑定到变量f f将在main函数范围的末尾销毁 - 因此"main"会因"async"而被Delay(1000)打印}。

    1. 创建异步匿名函数并开始执行。

      • 有一个Delay(1000)延迟"async"延迟立即打印。
    2. main执行继续。

      • "main"已打印。
    3. main范围的结束。

    4. 异步匿名函数被破坏。

      • main执行被阻止,直到功能完成。

      • "async"已打印。

答案 1 :(得分:6)

  

它打印MyGsonModelClass,这是否意味着主线程等到async main结束?

是的,但这是因为您没有从async捕获返回的未来。 async的特殊之处在于,从它返回的async在析构函数中阻塞,直到线程完成。由于您未捕获返回的future

future

必须在当前线程中进行之前完成,因为返回async(launch::async,[]() { Sleep(1000); puts("async"); }); 在表达式结束时被销毁。

  

打印future。这使得看起来主要不等待main async完成。

当您致电async时,您真正想要的是什么。由于您已捕获了未来,因此在完成异步任务时,允许主线程继续运行。由于你有一个延迟,线程async将在线程之前打印。

答案 2 :(得分:3)

如果您通过std::launch::async,则std::async必须运行任务,就像它在自己的线程中运行一样。

C ++中唯一的线程概念是std::thread

std::async返回具有唯一属性的std::future;如果被销毁,它会在完成std::async中存储的任务时阻止。当您未能捕获返回值时,这会陷阱;返回的std::future是一个未命名的临时存在,它会在该行的第34行结束时被销毁。

此破坏等待async任务完成。

在您存储它的情况下,此延迟会一直等到变量f被销毁,这是在main的末尾,这是在我们打印之后。

请注意,C ++ 11,MSVC 2015和2017的至少一个主要实现具有最佳边际兼容std::async,它使用线程池而不是新线程。此线程池意味着一组长时间运行的async调用可能会使其他async个调用无法运行。

使用线程池是合法的(只要它重新创建任何线程本地),但它应该尽量避免饥饿并在所有现有线程忙于“太长时间”的情况下创建新线程。

它略微合规,因为标准只规定线程"应该"取得进步。由于随机原因而永远不会进展的线程在C ++下是合法的;在某种意义上,你可以说这是std::async在这些情况下模仿的东西,从而通过了as-if测试。

答案 3 :(得分:0)

这是因为std::future(从std::async返回)的析构函数等待其任务完成。

在第一个代码段中,从std::future返回的临时std::async对象在语句末尾被销毁,因为用https://en.cppreference.com/w/cpp/language/lifetime编写

  

所有临时对象都被销毁,这是评估对象的最后一步   完全表达(按词法)包含它们所在的点   已创建

因此,在执行下一条语句之前,std::future对象的析构函数将阻塞,直到任务完成为止,这意味着puts("async")puts("main")之前执行。

但是,在第二个代码段中,std :: async的返回值被移动到本地对象中,该对象在退出范围时被破坏。因此,按预期,带有async的行将无阻塞地执行,并且puts("main")会在puts("async")(被Sleep调用阻塞)之前执行。

此行为在https://en.cppreference.com/w/cpp/thread/async中解释为:

  

如果从std :: async获得的std :: future没有移出或绑定   引用时,std :: future的析构函数将在   完整表达式的结尾,直到异步操作完成为止,   本质上使代码如下同步:

std::async(std::launch::async, []{ f(); }); // temporary's dtor waits for f()
std::async(std::launch::async, []{ g(); }); // does not start until f() completes

在书Effective Modern C++项目38 中,表示为:

  

最后一个析构函数的析构函数,指的是   通过std :: async块启动的非延迟任务,直到该任务   完成。本质上,这种未来的破坏者会   在异步执行任务的线程上进行隐式连接   正在运行。