C++ 在C++;11使线程池过时以避免昂贵的线程创建?

C++ 在C++;11使线程池过时以避免昂贵的线程创建?,c++,multithreading,asynchronous,c++11,threadpool,C++,Multithreading,Asynchronous,C++11,Threadpool,它与这个问题关系不大:。虽然问题不同,但意图相同: 问题1:使用您自己的(或第三方库)线程池来避免昂贵的线程创建是否仍然有意义? 另一个问题的结论是,您不能依靠std::thread进行池化(可能是,也可能不是)。然而,std::async(launch::async)似乎有更高的机会被合并 它不认为这是标准强制的,但是我希望所有好的C++11实现都会使用线程池,如果线程创建速度慢的话。只有在创建新线程成本较低的平台上,我希望它们总是生成新线程 问题2:这正是我的想法,但我没有事实证明。我很可能

它与这个问题关系不大:。虽然问题不同,但意图相同:

问题1:使用您自己的(或第三方库)线程池来避免昂贵的线程创建是否仍然有意义?

另一个问题的结论是,您不能依靠
std::thread
进行池化(可能是,也可能不是)。然而,
std::async(launch::async)
似乎有更高的机会被合并

它不认为这是标准强制的,但是我希望所有好的C++11实现都会使用线程池,如果线程创建速度慢的话。只有在创建新线程成本较低的平台上,我希望它们总是生成新线程

问题2:这正是我的想法,但我没有事实证明。我很可能弄错了。这是一个有根据的猜测吗?

最后,这里我提供了一些示例代码,首先展示了如何通过
async(launch::async)
来表示线程创建:

例1:

 thread t([]{ f(); });
 // ...
 t.join();
变成

 auto future = async(launch::async, []{ f(); });
 // ...
 future.wait();
 // a bit clumsy...
 auto dummy = async(launch::async, []{ f(); });

 // ... but I hope soon it can be simplified to
 async(launch::async, []{ f(); });
示例2:触发并忘记线程

 thread([]{ f(); }).detach();
变成

 auto future = async(launch::async, []{ f(); });
 // ...
 future.wait();
 // a bit clumsy...
 auto dummy = async(launch::async, []{ f(); });

 // ... but I hope soon it can be simplified to
 async(launch::async, []{ f(); });
问题3:您更喜欢
异步
版本而不是
线程
版本吗?


其余部分不再是问题的一部分,只是为了澄清:

为什么必须将返回值指定给虚拟变量

不幸的是,当前的C++11标准强制您捕获
std::async
的返回值,否则将执行析构函数,直到操作终止。有些人认为这是标准中的错误(例如,Herb Sutter)

下面的例子很好地说明了这一点:

{
  std::async(std::launch::async, []{ f(); });
  std::async(std::launch::async, []{ g(); });  // does not run until f() completes
}

另一项澄清:

我知道线程池可能有其他合法用途,但在这个问题上,我只对避免昂贵的线程创建成本方面感兴趣

我认为线程池在某些情况下仍然非常有用,特别是当您需要更多地控制资源时。 例如,服务器可能决定同时只处理固定数量的请求,以保证快速响应时间并提高内存使用的可预测性。这里的线程池应该可以

线程局部变量也可能是您自己的线程池的参数,但我不确定它在实践中是否相关:

  • 使用
    std::thread
    创建新线程时,不会启动初始化的线程局部变量。也许这不是你想要的
  • 在由
    async
    生成的线程中,我有点不清楚,因为该线程可能已被重用。根据我的理解,线程局部变量不能保证重置,但我可能弄错了
  • 另一方面,使用您自己的(固定大小)线程池,如果您真的需要它,可以让您完全控制它

    • 问题1

      我改变了原稿,因为原稿错了。我的印象是,在测试之后,我确定新线程中函数调用的开销与普通线程相比是巨大的。创建线程来处理函数调用的开销大约是普通函数调用的10000倍或更多倍。因此,如果要发出很多小函数调用,线程池可能是一个好主意

      很明显,用g++来传输的标准C++库没有线程池。但我可以肯定地看到他们的理由。即使需要通过某种线程间队列来处理调用的开销,它也可能比启动一个新线程便宜。标准允许这样做

      我的意思是,Linux内核人员应该努力使线程创建比目前更便宜。但是,标准C++库也应该考虑使用池来实现<代码>启动::异步启动::推迟< /COD> 操作是正确的,使用
      ::std::thread
      启动线程当然会强制创建新线程,而不是使用池中的线程。因此,首选
      ::std::async(::std::launch::async,…)

      问题2

      是的,基本上这个“隐式”启动一个线程。但事实上,发生的事情仍然很明显。所以我并不认为这个词含蓄地是一个特别好的词

      我也不认为强迫你在毁灭之前等待返回必然是一个错误。我不知道您是否应该使用
      async
      调用来创建预期不会返回的“守护进程”线程。如果期望它们返回,那么忽略异常是不好的

      问题3

      就我个人而言,我喜欢线程的启动是明确的。我非常重视可以保证串行访问的孤岛。否则,您将得到一个可变状态,您必须始终在某个地方包装一个互斥体,并记住使用它

      与“未来”模型相比,我更喜欢工作队列模型,因为存在“串行孤岛”,因此可以更有效地处理可变状态

      但实际上,这完全取决于你在做什么

      性能测试 因此,我测试了各种调用方法的性能,并在运行Fedora 29的8核(AMD Ryzen 7 2700X)系统上得出了这些数字,该系统使用clang 7.0.1版和libc++(而不是libstdc++)编译:

      在我的MacBook Pro 15(英特尔(R)Core(TM)i7-7820HQ CPU@2.90GHz)上,我的苹果LLVM版本10.0.0(clang-1000.10.44.4)在OSX 10.13.6下,我得到了以下信息:

         Do nothing calls per second:   22078079
              Empty calls per second:   21847547
         New thread calls per second:      43326
       Async launch calls per second:      58684
      Worker thread calls per second:    2053775
      
      对于工作线程,我启动了一个线程,然后使用一个无锁队列将请求发送到另一个线程,然后等待“完成”回复