当前位置: 首页 > 知识库问答 >
问题:

C 11中的async(launch::async)是否使线程池过时,以避免昂贵的线程创建?

佴普松
2023-03-14

它与这个问题有着松散的联系:std::thread是否在C11中合并?。虽然问题不同,但意图是一样的:

问题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();

示例2:发射并忘记线程

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

变成

 // 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)。

cppreference.com的这个例子很好地说明了这一点:

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

另一项澄清:

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

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

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

  • 使用std::thread创建新线程时,不会启动初始化的线程局部变量。也许这不是你想要的

共有1个答案

姜鸿畴
2023-03-14

问题一:

我改变了原来的版本,因为原来的版本是错误的。我的印象是Linux线程创建非常便宜,经过测试,我确定新线程中函数调用的开销与普通线程相比是巨大的。创建一个线程来处理函数调用的开销比普通函数调用慢10000倍或更多倍。所以,如果你发出很多小函数调用,线程池可能是个好主意。

很明显,与g一起提供的标准C库没有线程池。但我绝对可以为它们找到一个案例。即使必须将调用推入某种线程间队列的开销,它也可能比启动新线程更便宜。标准允许这样做。

我的意思是,Linux内核人员应该努力使线程创建比目前更便宜。但是,标准C库也应该考虑使用池来实现launch::async | launch::deferred

OP是正确的,使用::std::线程启动线程当然会强制创建一个新线程,而不是使用池中的线程。因此::std::async(::std::启动::async,...)是首选。

问题2:

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

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

问题3:

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

我喜欢工作队列模型比“未来”模型好得多,因为周围有“串行岛”,因此您可以更有效地处理可变状态。

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

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

   Do nothing calls per second:   35365257                                      
        Empty calls per second:   35210682                                      
   New thread calls per second:      62356                                      
 Async launch calls per second:      68869                                      
Worker thread calls per second:     970415                                      

和本机,在我的MacBook Pro 15"(英特尔(R)核心(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

对于工作线程,我启动了一个线程,然后使用一个无锁队列将请求发送到另一个线程,然后等待“完成”回复被发送回来。

“什么都不做”只是测试测试线束的头顶。

很明显,启动线程的开销是巨大的。在VM中的Fedora 25上,即使是具有线程间队列的工作线程也会将速度减慢20倍左右,在本机OS X上减慢约8倍。

我创建了一个OSDN室,其中保存了我用于性能测试的代码。可以在这里找到:https://osdn.net/users/omnifarious/pf/launch_thread_performance/

 类似资料:
  • 我是TPL的新手,我想知道:C#5.0新增的异步编程支持(通过新的异步和等待关键字)与线程的创建有什么关系? 具体来说,每次使用异步/等待是否都会创建一个新线程?如果有许多嵌套方法使用异步/等待,那么是否为这些方法中的每一个都创建了一个新线程?

  • 使用 时,线程池中最初创建了多少线程,Javadoc 没有指定任何数字,是否有一个保证的数字,我们最初总是会得到 10 个或其他什么。文档如下: newCachedThreadPool public static ExecutorService newCachedThreadPool()创建一个线程池,该线程池根据需要创建新的线程,但会在以前构建的线程可用时重用它们。这些池通常会提高执行许多短期异

  • 问题内容: 创建线程很昂贵。但是为什么价格昂贵呢?当创建Java线程使创建过程变得昂贵时,究竟发生了什么?我认为该说法是正确的,但是我只是对JVM中的线程创建机制感兴趣。 线程生命周期开销。线程创建和拆除不是免费的。实际开销因平台而异,但是线程创建会花费时间,从而在请求处理中引入延迟,并且需要JVM和OS进行某些处理活动。如果请求是频繁且轻量的(如在大多数服务器应用程序中一样),则为每个请求创建一

  • 如果我有一个固定大小的线程池,它什么时候真正调用启动线程?(它会在创建时启动它们吗?还是等到我开始提交任务时再启动?)

  • 问题内容: 基本上我需要在更多线程中运行〜数百个计算。我只想在paralell中运行一些并行线程,例如5个线程和5个计算。 我正在使用spring框架,@Async选项是自然选择。我不需要全功能的JMS队列,这对我来说有点麻烦。 有任何想法吗 ?谢谢 问题答案: 你检查了吗?你可以定义一个线程池,其中包含最大数量的线程来执行任务。 如果要与结合使用,请在spring-config中使用它:

  • 我对最新gcc中基于pthread和Ubuntu开发环境的线程的互斥锁和消息传递的性能感兴趣。一个很好的通用问题是用餐哲学家,每个哲学家使用lh和rh叉子与左右手邻居共享。我把哲学家的数量增加到99个,让我的四核处理器保持忙碌。 上面的代码允许我的哲学家尝试抓住他们需要的两个叉子。 上面的代码监控我的哲学家的进食或思考进度,这取决于他们是否能够保留这两个叉子。 在所有哲学家尝试自由选择后,等待所有