C++中多线程进程的内存布局

3

我对多线程进程中栈和堆的组织方式有些困惑:

  1. 每个线程都有自己的私有栈。
  2. 所有线程共享堆。
  3. 当程序动态创建线程(例如在Java中使用new Thread()),对象会被分配在堆上。

那么,堆是否包含用于线程对象的内存,也就是说,堆是否包含属于线程的栈?


我可能需要补充一点,就是并不存在“堆”。根据操作系统的不同,一个进程也可能会使用多个堆。例如,Windows 提供了 HeapCreate API。 - Jim Brissom
3个回答

4

这里的描述有意保持模糊,因为我们不想限制线程软件的实现者。

每个线程都有自己私有的堆栈。

由于每个线程执行的一组函数是互相独立的,它们需要存储返回地址等信息,因此每个线程都需要有自己的堆栈。

所有线程共享堆。

这是实现的最简单方法。这也意味着所有线程共享一个公共的内存块,因此每个线程可以通过修改内存来与其他线程通信。

当程序动态创建线程(例如Java中的new Thread())时,对象分配在堆上。

你在第一个问题中提到的堆栈。我们需要为它保留内存。因此,我们分配了一个堆的块并将其分配给线程,并说使用这个内存块来实现你的堆栈。(不是说它就是这样做的,但这是一种简单的技术方法)。

那么堆是否包含线程对象的内存,这意味着堆是否包含属于线程的堆栈?

在单线程程序中,可以将堆栈实现为堆的块。将堆栈和堆分开并向彼此增长只是一个概念。它们如何实现是未定义的,我们没有理由不能在堆内部实现堆栈。有关更多信息,请参见此问题:堆栈增长方向


1

将“堆栈”视为像任何其他数据结构一样的数据结构。它可以用许多方式实现。

以下是在2000年左右之前的C和C++程序中典型的堆栈实现描述。大多数仍然以这种方式实现:

有一段连续的内存地址范围被称为“堆栈”。通常,在具有内存控制器的系统上(对于英特尔来说,这意味着80386及更高版本),此内存地址范围的页面在使用之前不会分配给物理内存。通常,这个连续的地址范围出现在地址空间的末尾。
有一个堆栈指针,通常从内存区域的末尾开始。当创建新的堆栈帧时,堆栈指针会减少帧大小。CPU 有专门设计用于此操作的指令。如果访问了没有分配任何物理内存的内存区域,则操作系统处理页面错误并找到一些内存来分配给现在使用的页面。
所有未通过寄存器传递的本地变量和函数参数都会进入堆栈帧。
对于多线程程序,这种方案行不通,因此通常使用malloc或new分配一块内存区域,并使用调用该区域的指针和大小启动新线程。如果新线程需要比您分配的堆栈空间更多的空间,则可能发生各种可怕的事情,包括线程只是践踏一些随机内存,其中包括在堆上分配的其他变量。

但是,这绝不是实现堆栈的唯一方法。例如,您可以将堆栈实现为链表,其中列表的每个节点都是堆栈帧。支持称为“continuations”的结构的语言经常这样做。事实上,它们通常使用DAG,因为单个堆栈帧可能会生成多个其他同时有效的堆栈帧。

还有一件事情可以做,介于两者之间,其中您的节点只是包含几个堆栈帧的大型内存区域。当创建新帧超出节点时,在幕后分配另一个节点。

或者,所有局部变量都可以使用new或类似方法分配,并在超出范围时销毁。编译器可以在幕后完成此操作。

因此,在像Java这样甚至没有C或C ++中的指针的语言中,担心堆栈的确切位置或底层内存如何分配是有点愚蠢的。它甚至可能因不同的完全兼容JVM而异。

我会说通常情况下,C++中的pthreads实现堆栈的方式是在我描述C和C ++的历史工作方式的章节的最后一段所述的多线程程序的方式。它们通常还有一个“守护页”,即为堆栈分配区域开头的一个故意未映射的页面,以便运行时由于堆栈空间不足而导致程序通常会引发SEGV错误。(实际上,这似乎是一个过于简化而错误的解释,请查看Ben Voigt的评论获取守护页的真正用途)。


守卫页比这要复杂一些。最初分配了大量的地址空间,但实际上只有两个页面被提交。第一个页面是程序开始执行的地方,第二个页面标记了守卫属性。当访问守卫页时,会触发守卫信号/异常处理程序,该程序会提交下一个页面(并将其标记为守卫),并且该过程重复进行,直到保留的内存范围耗尽,此时将执行堆栈溢出处理程序。 - Ben Voigt
@Ben Voigt:那我误解了。哎呀。有趣的是,一种实现子线程的主线程堆栈行为的方法,这样你可以为最坏情况分配足够大的堆栈,但对于普通情况仍然很小。 - Omnifarious

0
每个堆栈都是在堆上创建的,只有少量内核从真正的“the one”堆栈中运行。

1
可能不是真的。线程堆栈通常在Windows上使用VirtualAlloc,在Unix和类Unix系统上使用mmap获取。 - Ben Voigt

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