两个或多个线程如何在它们分配的堆上共享内存?

Posted

技术标签:

【中文标题】两个或多个线程如何在它们分配的堆上共享内存?【英文标题】:How do two or more threads share memory on the heap that they have allocated? 【发布时间】:2012-08-08 04:52:53 【问题描述】:

正如标题所说,两个或多个线程如何共享它们分配的堆上的内存?我一直在考虑,但我不知道他们是如何做到的。以下是我对流程的理解,想必我在哪里搞错了。

任何线程都可以通过系统调用在堆上添加或删除给定数量的字节,该系统调用返回指向该数据的指针,大概是通过写入一个寄存器,然后线程可以将其复制到堆栈中。 所以两个线程 A 和 B 可以根据需要分配尽可能多的内存。但是我看不到线程 A 怎么知道线程 B 分配的内存在哪里。我也不知道任何一个线程如何知道另一个线程的堆栈所在的位置。多线程程序共享堆,我相信,可以访问彼此的堆栈,但我不知道如何。

我尝试搜索这个问题,但只找到了抽象出细节的特定语言版本。

编辑: 我试图不针对特定语言或操作系统,但我使用的是 Linux,并且从低级别的角度来看它,我猜是汇编。

【问题讨论】:

Do threads share the heap? 的可能重复项 不,我不这么认为。我在搜索时看到了那个,它没有询问线程如何共享堆,只有当他们这样做时才询问。我想确切地知道线程如何共享数据。沟通机制是什么?我认为他们共享指向分配内存的指针,但我不知道他们是如何做到的。 如何编辑我的问题以获得最佳清晰度?它最令人困惑的是什么? Usr 已经回答了我的问题,但我想确保我的问题对其他人来说是可以理解的,而且现在它对我来说似乎不是很干净。 【参考方案1】:

我对您的问题的解释:线程 A 如何知道指向 B 正在使用的内存的指针?他们如何交换数据?

答案:它们通常以指向公共内存区域的公共指针开头。这允许它们相互交换其他数据,包括指向其他​​数据的指针。

例子:

    主线程分配一些共享内存并将其位置存储在p 主线程启动两个工作线程,将指针@​​987654322@ 传递给它们 工作人员现在可以使用p 并处理p 指向的数据

在真正的语言 (C#) 中,它看起来像这样:

//start function ThreadProc and pass someData to it
new Thread(ThreadProc).Start(someData)

线程通常不会访问彼此的堆栈。一切都从传递给线程过程的一个指针开始。


创建线程是一项操作系统功能。它的工作原理是这样的:

    应用程序使用标准 ABI/API 调用操作系统 操作系统分配堆栈内存和内部数据结构 操作系统“伪造”第一个堆栈帧:它将指令指针设置为 ThreadProc 并将 someData “推入”堆栈。我说“伪造”是因为第一个堆栈帧不是自然产生的,而是由操作系统人为创建的。 操作系统调度线程。 ThreadProc 不知道它已在新堆栈上设置。它所知道的是 someData 位于它所期望的通常堆栈位置。

这就是 someData 到达 ThreadProc 的方式。这是第一个初始数据项的共享方式。步骤 1-3 由父线程同步执行。 4 发生在子线程上。

【讨论】:

主线程如何将指针p传递给工作线程?工作线程是否与主线程重复?我读到这就是创建新流程的工作原理,并且肯定会解释它如何传递数据,如果它是这样工作的,尽管不知道它们将如何区分。我更接近于汇编的观点,而不是更高级的语言。 我添加了对您评论的回复。 好的,因此多个线程可以通信的机制是通过在线程创建期间通过 OS 函数的参数将预定义的数据(通常是我想象的指针)推送到线程的堆栈来启动的。谢谢!这回答了我的问题,但我会等待一两天才能真正接受答案,以防有人写出更好或更清晰的回复。这对我的目的来说已经足够了,但是出于好奇,你能给操作系统函数更多的数据来推送到堆栈上吗?另外,这是一种特定于操作系统的机制,还是 Windows 或 Linux 的做法不同? 理论上这是特定于操作系统的,但实际上这是唯一的方法。一个指针总是足够的,因为您可以让该指针指向包含所有内容的任意结构。线程池线程可能会接收到(同步的)工作队列的指针。用户模式开发人员可以在这个简单的 API 之上放置任意包装器。 C# 甚至允许您隐式传入闭包对象。这是一个非常强大的包装器,它隐藏了我刚刚描述的所有内容。示例: int x = 0; new Thread(() => x++; ); 可以正常工作,并且全部构建在那个简单的 API 之上。 一个指针就够了,是的,但是可以添加更多数据吗?我只是好奇操作系统是否允许它。在实践中,这两种方式都无关紧要,因为就像你说的那样,一个就足够了。【参考方案2】:

从鸟瞰(1000 英里以上)的角度来看一个非常简短的答案: 线程是同一个进程的执行路径,堆实际上是属于进程的(并且是线程共享的结果)。每个线程只需要自己的堆栈即可作为单独的工作单元运行。

【讨论】:

【参考方案3】:

如果线程都使用同一个堆,则它们可以共享堆上的内存。默认情况下,大多数语言/框架都有一个默认堆,代码可以使用该堆从堆中分配内存。在非托管语言中,您通常会显式调用分配堆内存。例如,在 C 语言中,这可能是 malloc 等。在托管语言中,堆分配通常是自动的,分配方式取决于语言——通常通过使用new 运算符。但是,这稍微取决于上下文。如果您提供您要询问的操作系统或语言环境,我或许可以提供更多详细信息。

【讨论】:

【参考方案4】:

与属于同一进程的其他线程共享的线程:它的代码段、数据段和其他操作系统资源,例如打开的文件和信号。

【讨论】:

【参考方案5】:

您缺少的部分是静态内存,其中包含静态变量

此内存在程序启动时分配,并分配已知地址(在链接时确定)。所有线程都可以在不交换任何数据运行时访问此内存,因为地址是有效的硬编码。

一个简单的例子可能如下所示:

// Global variable.
std::atomic<int> common_var;

void thread1() 
  common_var = compute_some_value();


void thread2() 
  do_something();
  int current_value = common_var;
  do_more();


当然,全局值可以是一个指针,可以用来交换堆内存。生产者分配一些对象,消费者获取并使用它们。

// Global variable.
std::atomic<bool> produced;
SomeData* data_pointer;

void producer_thread() 
  while (true) 
    if (!produced) 
      SomeData* new_data = new SomeData();
      data_pointer = new_data;
      // Let the other thread know there is something to read.
      produced = true;
    
  


void consumer_thread() 
  while (true) 
    if (produced) 
      SomeData* my_data = data_pointer;
      data_pointer = nullptr;
      // Let the other thread know we took the data.
      produced = false;
      do_something_with(my_data);
      delete my_data;
    
  

请注意:这些不是优秀的并发代码示例,但它们显示了总体思路,没有太多混乱。

【讨论】:

以上是关于两个或多个线程如何在它们分配的堆上共享内存?的主要内容,如果未能解决你的问题,请参考以下文章

线程特定的堆分配

进程与线程

JVM-堆

TLAB

性能优化之 JVM 高级特性

是应该在堆栈还是堆上分配pthread函数参数?