多线程堆管理

Posted

技术标签:

【中文标题】多线程堆管理【英文标题】:Multithreaded Heap Management 【发布时间】:2011-01-10 14:53:27 【问题描述】:

在 C/C++ 中,我可以在一个线程中分配内存并在另一个线程中删除它。然而,每当从堆中请求内存时,堆分配器都需要遍历堆以找到适当大小的空闲区域。两个线程如何在不破坏堆的情况下有效地访问同一个堆? (这是通过锁定堆来完成的吗?)

【问题讨论】:

重新标记,因为这实际上与任何特定的编程语言无关。 不完全正确。仅在增加堆时才涉及操作系统(这涉及在新内存页面中进行分页)。由 malloc/new 的 C/C++ 实现来实际管理堆。 【参考方案1】:

是的,通常必须锁定对堆的访问。每当您拥有共享资源时,都需要保护该资源;内存是一种资源。

【讨论】:

即使每个线程都管理自己的内存?这听起来非常低效。 @deus:不,但这不是你描述的情况。你说线程正在共享内存。 (在另一个线程中删除。)【参考方案2】:

这在很大程度上取决于您的平台/操作系统,但我相信这在主要系统上通常是可以的。 C/C++ 没有定义线程,所以默认情况下我相信答案是“堆不受保护”,你必须为你的堆访问提供某种多线程保护。

但是,至少对于 linux 和 gcc,我相信启用 -pthread 会自动为您提供这种保护...

另外,还有一个相关的问题:

C++ new operator thread safety in linux and gcc 4

【讨论】:

【参考方案3】:

这是一个操作系统问题,因此答案将取决于操作系统。

在 Windows 上,每个进程都有自己的堆。这意味着同一进程中的多个线程(默认情况下)共享一个堆。因此,操作系统必须线程同步其分配和释放调用以防止堆损坏。如果您不喜欢可能发生的争用的想法,您可以使用Heap* routines 绕过它。您甚至可以重载 malloc(在 C 中)和 new(在 C++ 中)来调用它们。

【讨论】:

【参考方案4】:

是的,支持多线程代码的“普通”堆实现必然包括某种锁定以确保正确操作。在相当极端的条件下(很多堆活动),这可能会成为瓶颈;更专业的堆(通常提供某种线程本地堆)可以在这种情况下提供帮助。我用过英特尔 TBB 的 "scalable allocator" 到 good effect。 tcmalloc 和 jemalloc 是考虑到多线程扩展的其他 malloc 示例。

单线程和多线程感知 malloc here 之间的一些时序比较比较。

【讨论】:

只是出于兴趣 gcc 和 MSVC 的 malloc 策略是什么? 好问题。不太了解 MSVC 的 CRT,但 gcc 通常与使用 ptmalloc 的 glibc 相关联:en.wikipedia.org/wiki/Malloc#dlmalloc_.28the_glibc_allocator.29。上面的时序链接很好地显示了这种扩展,这可以解释为什么我自己对 TBB 分配器的实验有时会使事情变得更好,有时会变得更糟。 @doron Windows Vista 和更新版本使用低碎片堆,据说这使得标准 malloc 在多线程程序中运行良好。【参考方案5】:

我找到了this 链接。

基本上,堆可以划分为arena。请求内存时,依次检查每个arena是否被锁定。这意味着不同的线程可以同时安全地访问堆的不同部分。 Frees 有点复杂,因为每个 frees 都必须从分配它的 arena 中释放出来。我想一个好的实现会让不同的线程默认到不同的领域,以尽量减少争用。

【讨论】:

【参考方案6】:

一般来说,您无需担心内存分配器的线程安全性。所有标准内存分配器——即 MacOS、Windows、Linux 等附带的内存分配器——都是线程安全的。锁是提供线程安全的标准方式,尽管可以编写只使用原子操作而不是锁的内存分配器。

现在,这些内存分配器是否缩放; 是一个完全不同的问题;也就是说,它们的性能是否与执行内存操作的线程数无关?大多数情况下,答案是否定的;它们要么减慢速度,要么会消耗大量更多内存。在两个维度(速度和空间)上的第一个可扩展分配器是Hoard(我写的); Mac OS X 分配器受到它的启发——并在文档中引用了它——但 Hoard 更快。还有其他的,包括 Google 的 tcmalloc。

【讨论】:

你能提供一些关于霍德所采用的一般策略的信息吗? 内存在称为超级块的块中进行管理,其中包含相同大小的对象。每个线程都会获得一些这些(线程本地),这意味着没有锁或争用。线程被多路复用到每个 CPU 堆上,其中包含超级块。超级块的分配一次只能由一个线程完成,从而限制了错误共享。 Hoard 通过在每个 CPU 堆变空时将大部分为空的超级块移动到共享堆来限制内存消耗——在确保渐近优化内存消耗的同时限制争用。见cs.umass.edu/~emery/hoard/asplos2000.pdf。

以上是关于多线程堆管理的主要内容,如果未能解决你的问题,请参考以下文章

使用 QtConcurrent 在 QT 中进行多线程

多线程之间的资源共享

多线程

JAVA-初步认识-第十二章-JVM中的多线程分析

线程基础知识

不堆概念换个角度聊多线程并发编程