Stroustrup引用的std :: async的限制是什么?

时间:2014-10-15 01:12:12

标签: multithreading c++11 asynchronous concurrency language-lawyer

在他的书 The C ++ Programming Language (第4版)的“教程”第5.3.5.3章中,Bjarne Stroustrup撰写了关于std::async函数的文章。

  

有一个明显的局限性:甚至不要考虑将async()用于共享需要锁定的资源的任务 - 使用async()您甚至不知道有多少thread会被使用,因为根据它对通话时可用系统资源的了解,最多可以async()来决定。

the C++11-FAQ on his website中可以找到类似的劝告。

  

“简单”是async() / future设计中最重要的方面;期货也可以与线程一起使用,但是甚至认为使用async()来启动执行I / O,操作互斥锁或以其他方式与其他任务交互的任务

有趣的是,当他在他的书第42.4.6节中更详细地回到C ++ 11的并发特性时,他没有详细说明这个限制。更有趣的是,在本章中他实际上还在继续(与他网站上的陈述相比):

  

async()的简单实际用法是生成一个任务来收集用户的输入。

The documentation of async on cppreference.com根本没有提到任何此类限制。

在阅读了最终形式导致C ++ 11标准的一些提案和讨论之后(遗憾的是我无法访问),我理解async很晚才纳入C + +11标准,并且有很多讨论这个功能应该有多强大。特别是,我发现文章Async Tasks in C++11: Not Quite There Yet by Bartosz Milewski是对实施async时必须考虑的问题的非常好的总结。

但是,所有讨论的问题都与thread_local变量有关,如果线程被回收,则不会被破坏,如果线程在操作中切换任务并且两个任务都保持{{1},则会发生虚假死锁或数据访问冲突分别是{}或mutex等等。这些是该功能的实现者的严重问题,但如果我理解正确,recursive_mutex的当前规范要求通过在调用者的线程或上执行任务来隐藏用户所有这些细节,就好像为该任务创建了一个新线程。

所以我的问题是:我不允许对async使用async手动使用thread做什么,这个限制的原因是什么?

例如,以下程序有什么问题吗?

#include <future>
#include <iostream>
#include <mutex>
#include <vector>

static int tally {};
static std::mutex tally_mutex {};

static void
do_work(const int amount)
{
  for (int i = 0; i < amount; ++i)
    {
      // Might do something actually useful...
      const std::unique_lock<std::mutex> lock {tally_mutex};
      tally += 1;
    }
}

int
main()
{
  constexpr int concurrency {10};
  constexpr int amount {1000000};
  std::vector<std::future<void>> futures {};
  for (int t = 0; t < concurrency; ++t)
    futures.push_back(std::async(do_work, amount / concurrency));
  for (auto& future : futures)
    future.get();
  std::cout << tally << std::endl;
}

显然,如果运行时决定在主线程上安排所有任务,我们将毫无理由地一遍又一遍地不必要地获取互斥锁。但是,虽然这可能效率低下,但不正确

2 个答案:

答案 0 :(得分:5)

&#34;问题&#34;使用std :: async是默认情况下你不知道它是否启动一个线程。如果你的函数需要在一个单独的线程上运行,那么这是一个问题,因为在调用get()或wait()之前你的函数可能不会运行。 你可以传递std :: launch :: async来确保函数在它自己的线程上启动,这就像一个不能分离BG的std :: thread。

答案 1 :(得分:0)

你指出了这个问题。 线程可以回收...... 因此,任何使用thread_local存储都是危险的。

你的循环只是 - 但正如你所提到的,可能效率低下。

您可以要求该语言使用std :: launch :: async生成另一个线程。 但是线程可能仍然可以回收。