【问题标题】:Stack and Heap allocation堆栈和堆分配
【发布时间】:2012-06-26 17:51:01
【问题描述】:

我正在进一步研究内存模型,并且正在努力了解进程中存在多少堆。

所以,如果我们有 1 个进程,其中有 5 个线程,我是否正确地说我们有 5 个堆栈和 1 个堆?

如果是这样,线程是否可以访问彼此的堆栈(或者这正是它们具有单独堆栈以防止损坏的原因),如果只有 1 个堆,那么显然它们都访问该堆,因此需要锁定多线程?我理解正确吗?

【问题讨论】:

    标签: c# multithreading


    【解决方案1】:

    简而言之,是的。

    进程中的所有线程共享同一个堆,因此它们可以交换数据。每个线程都有自己的堆栈,该堆栈与该线程上的当前代码执行相关。

    这里有一个非常好的线程资源:http://www.albahari.com/threading/

    线程类似于操作系统进程,您的 应用程序运行。就像进程在计算机上并行运行一样, 线程在单个进程中并行运行。流程完全 彼此隔离;线程只有有限的程度 隔离。特别是,线程与其他线程共享(堆)内存 在同一应用程序中运行的线程。这在一定程度上是为什么 线程很有用:一个线程可以在后台获取数据,例如 实例,而另一个线程可以在数据到达时显示它。

    【讨论】:

      【解决方案2】:

      这是实现定义的,但让我们谈谈最流行的现代操作系统,因为您添加了 C# 标签。

      一个进程中存在多少堆。

      通常每个进程 1 个。

      如果我们有 1 个进程,其中有 5 个线程,我是否正确地说我们会有 5 个堆栈和 1 个堆?

      是的。每个线程立即消耗 1MB 的虚拟地址空间用于线程堆栈。

      如果是这样,线程是否可以访问彼此的堆栈(或者这正是它们具有单独堆栈以防止损坏的原因),如果只有 1 个堆,那么显然它们都访问该堆,因此需要锁定多线程?我理解正确吗?

      是的,现代环境的沙盒效果非常好,因此您无法直接从另一个线程访问其他线程堆栈。

      【讨论】:

      • Lukas 你看到 Jakub-Konecki 的回答了吗?它与“......因此您无法访问其他线程堆栈”有什么关系?为什么存在“锁定”机制?
      • 您当然可以从另一个线程访问一个线程的堆栈,但最好避免这种直接通信并使用可从两个线程访问的对象队列。
      • 吹毛求疵:一个进程有(正常的)托管堆,一个大对象堆,你可以认为非托管堆是#3。
      • @MartinJames - 访问另一个线程堆栈肯定不容易或不常见。也许有指针。
      • @MartinJames 在本机代码中肯定,在 .Net 中很复杂。默认情况下是关闭的。用“不安全”是可能的
      【解决方案3】:

      线程是在单个进程的同一虚拟地址空间中同时运行的独立指令流。堆是系统提供给每个进程供其私人使用的一大块内存。进程可以调整它们的堆大小,并且可以根据需要使用堆空间。线程可以协作使用此堆空间,还可以分配称为 线程本地存储 (TLSes) 的额外私有内存区域。

      由于所有线程共享相同的虚拟地址空间,它们可以直接访问彼此的堆栈内存。这意味着一个线程可以将其堆栈上的变量作为参数传递给在其他线程中运行的函数。但是线程堆栈仍然是独立的,因为一个线程从不将值推送或弹出到其他线程的堆栈中,而只会将其推送到自己的堆栈空间中。由于 x86 和 x86-64 上的堆栈向下增长,因此每个线程的堆栈内存底部都有一个特殊的页面 - 所谓的 保护页面。如果在操作堆栈时到达保护页,则会发生堆栈错误。

      在 C 和 C++ 等非托管语言中,进程内存的每个部分都可以通过使用指针随意访问。一个线程可以完全弄乱另一个线程堆栈的内容,从而使第二个线程(以及整个进程)崩溃。在 C# 中,这些事情不会发生在 unsafe 块之外,因为堆栈是由 CLR 管理的。

      【讨论】:

      • 一个线程可以将其堆栈上的变量作为参数传递给在其他线程中运行的函数 - 你将如何做到这一点?
      • 使用 Win32 线程,您可以启动一个新线程并将当前线程中的局部变量的地址作为线程参数传递。您还可以使用PostThreadMessage 向已经运行的线程发送消息。
      • 您可以通过值传递变量,也可以使用 C/C++ 中的指针(或 C++ 中仍以指针实现的别名)通过地址传递变量。
      【解决方案4】:

      是的,每个线程都有自己的堆栈。这是非常必要的,堆栈会跟踪方法完成后返回的位置,并存储返回地址。由于每个线程都执行自己的代码,因此它们需要自己的堆栈。局部变量和方法参数也存储在那里,使它们(通常)是线程安全的。

      堆的数量是一个更复杂的细节。您正在为垃圾收集堆计数 1。从实现的角度来看,这并不完全正确,三代堆加上大对象堆在逻辑上是不同的堆,最多加起来四个。当您分配过多时,此实现细节开始变得重要。

      在托管代码中您不能完全忽略的另一个是存储静态变量的堆。它与 AppDomain 相关联,静态变量与 AppDomain 一样存在。在 .NET 文献中通常称为“加载程序堆”。它实际上由 3 个堆(高频堆、低频堆和存根堆)组成,jitted 代码和类型数据也存储在那里,但这已经很重要了。

      忽略列表的下方是本机代码使用的堆。其中两个很容易从元帅级中看到。有一个默认进程堆,Windows 从它分配,Marshal.AllocHGlobal() 也是如此。还有一个单独的堆,COM 存储数据,Marshal.AllocCoTaskMem() 从中分配。最后,您与之互操作的任何本机代码都将拥有自己的堆以支持其运行时支持。此类代码使用的堆数量仅受加载到您的进程中的本机 DLL 数量的限制。所有这些堆都存在,您几乎不会直接处理它们。

      所以,最少 10 个堆。

      【讨论】:

        猜你喜欢
        • 2017-08-12
        • 2011-10-06
        • 1970-01-01
        • 2019-05-17
        • 2011-10-09
        • 2017-01-04
        • 2012-06-05
        • 2012-12-05
        • 2011-05-28
        相关资源
        最近更新 更多