【问题标题】:Memory management while using threads使用线程时的内存管理
【发布时间】:2015-03-19 21:26:21
【问题描述】:

1) 我尝试搜索在程序中使用线程时如何分配内存但找不到答案。这里What and where are the stack and heap? 是调用单个程序时堆栈和堆的工作方式。但是当涉及到线程编程时会发生什么?

2) 使用 OpenMP 并行区域创建线程,并行代码将在每个线程中同时执行。与顺序执行的相同代码占用的内存相比,这是否会在内存中分配更多空间?

【问题讨论】:

    标签: multithreading memory-management openmp heap-memory stack-memory


    【解决方案1】:

    一般来说,是的,[用户空间]堆栈是每个线程一个,而堆通常由所有线程共享。参见例如this Linux question。但是,在某些操作系统 (OS) 上,在 Windows in particular 上,即使是单线程应用程序也可能使用多个堆。使用 OpenMP 进行线程处理并不会改变这些主要依赖于操作系统的基础知识。因此,除非您将问题缩小到特定的操作系统,否则在这种笼统的层面上不能说更多。

    由于我懒得自己画这个,这里是 Nichols 等人的 PThreads Programming 的对比图。 (1996)

    在免费的 LLNL POSIX Threads Programming tutorial by B. Barney. 中找到了一个更详细(可惜可能更令人困惑)的图表

    是的,正如您正确怀疑的那样,运行更多线程确实会消耗更多堆栈内存。你实际上可以exhaust the virtual address space of a process just with thread stacks if you make enough of them。 OpenMP 的各种实现都有一个STACKSIZE environment variable(或thereabout)来控制OpenMP 为线程分配多少堆栈。

    关于 Z boson 关于线程本地存储 (TLS) 的问题/建议:大致(即概念上)说,Thread Local Storage 是每个线程堆。与用于操作它的 API 中的每个进程堆有所不同,至少因为每个线程都需要自己的指向其自己 TLS 的单独指针,但基本上你有一个类似堆的进程地址空间块被保留到每个线程。 TLS 是可选的,您不必使用它。 OpenMP provides its own abstraction/directive for TLS-like persistent per-thread data,叫THREADPRIVATE。 OpenMP THREADPRIVATE 没有必要使用操作系统的 TLS 支持,但是至少在那个环境中,有一个 Linux-focused paper which says that such an implementation gave the best performance

    这里有一个微妙之处(或者为什么我在将 TLS 与每线程堆进行比较时说“粗略地说”):假设您想要一个每线程堆,例如,以减少对主堆的锁定争用。您实际上不必在每个线程的 TLS 中存储整个每个线程的堆。 It suffices 在每个线程的 TLS 中存储一个不同的头指针,指向在共享的每个进程空间中分配的堆。在程序中识别并自动使用每线程堆(以减少主堆上的锁定争用)是farily difficult CS problem。自动执行此操作的堆分配器称为 scalable/parallel[izing] 堆分配器 或类似的名称。例如,Intel TBB provides one such allocator,它是can be used in your program even if you use nothing else from TBB。尽管有些人似乎认为 Intel 的 TBB 分配器包含黑魔法,但实际上它与前面提到的使用 TLS 指向某个线程本地堆的基本思想并无本质区别,后者又由几个按块隔离的双向链表组成/object-size,如下图 Intel paper on TBB 所示:

    IBM 有一些与 AIX 7.1 相当相似的东西,但更复杂一些。您可以告诉其(默认)分配器为多线程应用程序使用固定数量的堆,例如MALLOCOPTIONS=multiheap:3。 AIX 7.1 还有另一个选项(可以与多堆组合)MALLOCOPTIONS=threadcache,这看起来有点类似于 Intel TBB 所做的,因为它保留了已释放区域的每个线程缓存,未来的分配请求可以通过它来处理更少的全局堆争用。除了默认分配器的那些选项之外,AIX 7.1 还具有(非默认)"Watson2" allocator,它“使用特定于线程的机制,该机制使用不同数量的堆结构,这取决于程序的行为。因此无需配置选项是必需的。” (但您确实需要使用MALLOCTYPE=Watson2 明确选择此分配器。)Watson2 的操作听起来更接近英特尔 TBB 分配器的操作。

    上面详述的上述两个示例(Intel TBB 和 AIX)只是作为具体示例,但不应被理解为拥有一些独家优势。每个线程或每个 CPU 堆缓存/arena/magazine 的想法相当普遍。 BSDcan jemalloc paper 引用 1998 MS Research paper 作为第一个为此目的系统地评估竞技场的人。前面提到的 MS 论文确实引用了 ptmalloc 网页“于 1998 年 5 月 11 日访问”,并将 ptmalloc 的工作总结如下:“它使用子堆的链接列表,其中每个子堆都有一个锁、128 个空闲列表和一些内存来管理. 当一个线程需要分配一个块时,它会扫描子堆列表并抓取第一个未锁定的块,分配所需的块,然后返回。如果找不到未锁定的子堆,则创建一个新的子堆并将其添加到列表。这样,线程永远不会在锁定的子堆上等待。"

    【讨论】:

    • @Respwned Fluff 在 geeksforgeeks.org/memory-layout-of-c-program> 中会有 c 程序的内存布局。每个线程的每个并行区域代码副本是否也在此进程文本区域内占用一些空间?
    • 这取决于应用程序。对于某些应用程序,线程执行的代码是相同的;对于其他应用程序,每个线程都可以非常专业化,因此它们之间没有实际的代码共享。但是所有线程(根据定义)都可以访问整个进程地址空间,因此理论上所有线程都可以使用/共享相同的代码。顺便说一下,这个代码共享(或非共享)问题与堆栈是正交的。
    • 我没有得到“与堆栈正交”这个词。这是什么意思..?
    • 这意味着无论线程是否运行相同的代码,每个堆栈都有一个线程。请注意,文本段对所有线程都是共享的和可见的,但这并不意味着实际上它们都将在同一代码区域中运行。我在答案中添加了图表,稍后我将添加指向另一个来源的链接(有更多解释)。
    • 值得一提的是,GLIBC 中的内存分配器(ptmalloc2,如果我没记错的话)很长时间以来就支持每线程领域和一些服务器分发,例如RHEL 附带启用它,而其他(主要是桌面)发行版不这样做,因为它增加了应用程序的内存占用。 ptmalloc2 每个 CPU 核心使用一个 arena,并且大部分是无锁的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-19
    • 1970-01-01
    • 1970-01-01
    • 2011-08-29
    • 2010-09-30
    • 1970-01-01
    相关资源
    最近更新 更多