安全的线程栈大小是多少?

5
我正在编写一些代码,会创建许多线程(目前大约有512个,但将来可能会更高)。每个线程只执行少量操作,因此我希望线程对系统的开销最小化。
我正在使用 pthread_attr_setstacksize() 设置堆栈大小,并且可以从 PTHREAD_STACK_MIN 获取允许的最小堆栈大小。但我的问题是:是否安全使用 PTHREAD_STACK_MIN 作为线程堆栈大小? 我该如何计算需要多少堆栈?是否有任何隐藏的开销需要添加到我的计算中?
此外,还有其他技术可以用来减轻线程对系统的负担吗?
3个回答

7

您不应该创建如此多的线程,也绝对不应该为了执行一些简单的操作而创建新线程。只有在现有线程已经完全饱和并且还有更多可用的物理或逻辑处理器来执行工作时,才应该创建新的线程。这限制了合理当前应用程序的最大线程数约为10个左右,即使在六核心上运行,最多也只需要12个。这样的设计非常有缺陷,会使用大量的进程内存,并不会真正提高性能。

至于堆栈大小,您无法真正计算出任意线程所需的数量,因为它完全取决于运行代码。但是,在Visual Studio中,典型的堆栈大小为几兆字节。您需要发布整个代码以及线程执行的结果反汇编,才能知道需要使用多少堆栈大小。只需将其固定在几兆字节即可。


1
+1:但是如果您的线程经常受到IO限制,您可能希望拥有比核心更多的线程... - Oliver Charlesworth
4
@C. Ross: 这取决于它们有多么受 I/O 限制。好的,因此还有其他构建异步 I/O 系统的方法,这些方法在资源方面更加高效(可能在我的时间方面效率较低,这取决于我的事件队列、select等技能水平),但如果我的线程将花费 99.8% 的时间等待网络 I/O,那么我会需要500个线程。有时候内存比程序员便宜,有时候则不是;-) - Steve Jessop
1
@DreadMG:感谢您的回复。也许我应该更明确地说明我正在编写的代码。我的程序非常受IO限制。我试图接近我的SSD规定的最大IOPS数量(约为16,000)。我正在从一个大文件(约4GB)中读取许多(约66,000)小的(<512B),稀疏的数据块。我需要尽可能快地检索这些信息。到目前为止,我发现最佳线程数约为512,但如果我可以减少它们的开销,那么也许我可以使用更多线程。这是在一台专用服务器上运行的,这是它唯一的工作。速度就是一切! - Lee Netherton
@Steve 真的,没错。但如果你想这样做,请确保你运行的是64位系统,并且有足够的内存。 - C. Ross
@ltn100:你没提到这个!:P 如果你只是做一点I/O,那么你可能根本不需要太多的堆栈空间。我仍然认为,如果你正在使用512个线程,那么你在I/O代码中肯定出了很大的问题。为什么不直接将整个文件映射到内存并从堆中读取呢? - Puppy
@DreadMG:抱歉,我只是想用简洁的问题来表达,但似乎事与愿违了!我已经尝试过使用mmap,当它命中缓存时非常快,但当它未命中时非常慢(即使我只对几十个字节感兴趣,它也必须加载整个页面[4096字节])。我还尝试过AIO,但遇到了各种麻烦,但那是另一天的另一个故事... - Lee Netherton

5

所需的堆栈帧大小取决于您使用的编译器,基本上您可以尝试猜测自动变量、参数和一些开销的大小(例如返回地址、保存寄存器等)。

您应该考虑是否可以使用线程池作为替代方案。因为创建线程并不是免费的。


1
我对线程池在现代实现中是否有显著好处持怀疑态度。没有人再使用LinuxThreads和类似的hack技巧了。你有关于线程创建相对成本的最新参考资料吗? - R.. GitHub STOP HELPING ICE
1
确实。线程创建的成本因情况而异,但一般来说现在很便宜。然而,我认为关于线程池的观点仍然存在。它们是一个值得考虑的有用工具,但我发现大多数人只是试图找到使用它们的理由。 - Matt Joiner

4

减少线程的栈大小不会降低开销(无论是CPU、内存使用还是性能)。在这方面,您唯一的限制是平台给予线程的总可用虚拟地址空间。

我建议在出现问题之前(如果有的话),使用默认的栈大小。然后,在出现问题时尽可能地减少栈的使用。然而,这可能导致真正的性能问题,因为您将需要访问堆,或者设计线程相关的分配

隐藏的开销可能包括:

  • 在栈上分配大数组,例如VLAalloca()或普通的静态大小自动数组。
  • 代码不受您控制或您不知道使用后果的代码,例如模板、工厂类等。然而,考虑到您没有指定C++,这种情况可能不太可能发生。
  • 从库头文件等导入的代码。这些可能会在版本之间进行更改,并显着改变它们的堆栈,甚至线程使用。
  • 递归。这是由于以上几点引起的,考虑像boost::bind、可变参数模板、疯狂的宏,以及使用缓冲区或大对象的一般递归。

除了设置堆栈大小外,您还可以操作线程优先级,并根据需要挂起和恢复它们,这将显着帮助调度程序和系统响应能力。Pthreads允许您设置争用范围;LWP和范围内调度在其性能特征方面差异很大。

以下是一些有用的链接:


谢谢Matt。我认为这是我想要的答案最接近的了。我希望有人会说“将所有本地变量的sizeof()相加,再加上42字节的线程开销,然后再加上10%的好运气”,但似乎可能还有很多其他因素在起作用。感谢您对优先级和挂起的提示。 - Lee Netherton
2
@ltn100:不,更像是“使用指定的堆栈大小彻底测试您的代码,在检测溢出的调试上下文中。然后再加上10%的好运气”。 - Steve Jessop
2
@ltn100:如果你能找到一个在你的平台上实际测量高水位堆栈使用情况的调试工具,那就很好了,但实际需要这样做的平台(因为堆栈是物理RAM)不一定拥有最好的调试工具。而且你必须非常彻底地测试。任何小事情,包括对动态链接库进行的更改,或者它们查看的环境变量,甚至是星期几,都可能增加你的高水位标记。避免使用可变长度数组,避免任何依赖于输入数据的递归。 - Steve Jessop
@Steve Jessop:非常正确。术语用得好,还警告了外部代码/递归的问题。 - Matt Joiner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接