77

函数是malloc()可重入的吗?

4

12 回答 12

105

问题:“malloc 是可重入的”?
答:不,不是。以下是例程可重入的一个定义。

malloc 的所有常见版本都不允许您重新输入它(例如,从信号处理程序)。请注意,可重入例程可能不使用锁,并且几乎所有现有的 malloc 版本都使用锁(这使它们成为线程安全的)或全局/静态变量(这使得它们成为线程不安全不可重入的)。

到目前为止,所有答案都回答“malloc 线程安全吗?”,这是一个完全不同的问题。对于这个问题,答案是它取决于您的运行时库,并且可能取决于您使用的编译器标志。在任何现代 UNIX 上,默认情况下您都会获得线程安全的 malloc。在 Windows 上,使用、/MT或标志来获取线程安全的运行时库。/MTd/MD/MDd

于 2009-05-13T05:15:32.970 回答
49

我在某处读到,如果您使用 -pthread 进行编译,malloc 将成为线程安全的。我很确定它的实现依赖于它,因为 malloc 是 ANSI C 而线程不是。

如果我们在谈论 gcc:

在 x86 和 AMD64 上,使用 -pthread 和 malloc() 进行编译和链接将是线程安全的。

http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2431a99b9bdcef11/ea800579e40f7fa4

另一种观点,更有见地

glibc-2.2+ 的 {malloc, calloc, realloc, free, posix_memalign} 是线程安全的

http://linux.derkeiler.com/Newsgroups/comp.os.linux.development.apps/2005-07/0323.html

于 2009-05-13T02:26:31.300 回答
15

这是一个很老的问题,我想根据当前的情况带来新鲜感。

是的,目前malloc()是线程安全的。

来自GNU C 库参考手册glibc-2.20 [released 2014-09-07]

void * malloc (size_t size)

初步:MT-Safe | ...

... 1.2.2.1 POSIX 安全概念:

... MT-Safe 或 Thread-Safe 函数在存在其他线程的情况下可以安全调用。MT,在 MT-Safe 中,代表多线程。

MT-Safe 并不意味着函数是原子的,也不意味着它使用 POSIX 向用户公开的任何内存同步机制。甚至有可能按顺序调用 MT-Safe 函数不会产生 MT-Safe 组合。例如,让一个线程一个接一个地调用两个 MT-Safe 函数并不能保证等同于原子执行这两个函数的组合的行为,因为其他线程中的并发调用可能会以破坏性方式干扰。

可以跨库接口内联函数的整个程序优化可能会暴露不安全的重新排序,因此不建议跨 GNU C 库接口执行内联。记录的 MT 安全状态不能保证在整个程序优化下。但是,在用户可见的标头中定义的函数被设计为内联安全。

于 2014-11-20T18:37:17.563 回答
9

是的,在POSIX.1-2008 malloc下是线程安全的。

2.9.1 线程安全

本卷 POSIX.1-2008 定义的所有函数都应是线程安全的,但以下函数 1 不需要是线程安全的。

[不包含的函数列表malloc]

于 2015-04-04T01:30:01.533 回答
9

这是 glibc 的 malloc.c 的摘录:

线程安全:线程安全,除非定义了 NO_THREADS

假设 NO_THREADS 默认没有定义, malloc 至少在 linux 上是线程安全的。

于 2010-09-20T14:50:36.240 回答
7

如果您正在使用 GLIBC,答案是:是的,但是。

具体来说,是的,但是,请注意,虽然 malloc 和 free 是线程安全的,但调试功能不是。

具体来说,极其有用的 mtrace()、mcheck() 和 mprobe() 函数不是线程安全的。这是您从 GNU 项目中看到的最短、最直接的答案之一,这里对此进行了解释:

https://sourceware.org/bugzilla/show_bug.cgi?id=9939

您将需要考虑替代技术,例如 ElectricFence、valgrind、dmalloc 等。

所以,如果你的意思是“malloc() 和 free() 函数是线程安全的”,答案是肯定的。但是,如果您的意思是“是整个 malloc/free套件线程安全”,那么答案是否定的。

于 2017-03-17T18:44:35.280 回答
6

简短回答:是的,从 C11 开始,这是包含线程概念的 C 标准的第一个版本,malloc要求朋友是线程安全的。许多同时包含线程和 C 运行时的操作系统在 C 标准之前很久就做出了这种保证,但我不准备向所有人发誓。然而,malloc朋友们没有也从来没有被要求是可重入的。

这意味着,同时从多个线程调用mallocfree从多个线程调用是安全的,而不必担心锁定,只要您不违反任何其他内存分配规则(例如free,在每个返回的指针上调用一次且仅调用一次malloc)。但是从可能中断了对处理信号的线程的调用或在处理信号的线程中的信号处理程序调用这些函数是不安全的。有时,使用 ISO C 之外的功能,您可以保证处理信号的线程不会中断对or的调用,例如使用and ,但除非您别无选择,否则尽量不要这样做,因为很难完全正确。mallocfreemallocfreesigprocmasksigpause


引文的长答案:C 标准在2011 修订版中添加了线程的概念(链接是文档 N1570,这是与免费公开的 2011 标准的官方文本最接近的近似值)。在该修订版中,第 7.1.4 节第 5 段规定:

除非在随后的详细描述中另有明确说明,否则库函数应防止数据竞争如下: 库函数不应直接或间接访问当前线程以外的线程可访问的对象,除非通过函数的参数直接或间接访问对象. 库函数不得直接或间接修改当前线程以外的线程可访问的对象,除非通过函数的非常量参数直接或间接访问对象。如果对象对用户不可见并且受到保护以防止数据竞争,则实现可以在线程之间共享它们自己的内部对象。

[脚注:这意味着,例如,不允许实现在没有同步的情况下将静态对象用于内部目的,因为即使在没有显式在线程之间共享对象的程序中,它也可能导致数据竞争。同样,不允许 memcpy 的实现复制超出目标对象指定长度的字节,然后恢复原始值,因为如果程序在线程之间共享这些字节,可能会导致数据竞争。]

据我了解,这是一种冗长的说法,即 C 标准定义的库函数必须是线程安全的(通常意义上:您可以同时从多个线程调用它们,而无需自己进行任何锁定,只要它们最终不会与作为参数传递的数据发生冲突),除非特定函数的文档明确说明它不是。

然后,7.22.3p2确认 malloc、calloc、realloc、aligned_alloc 和 free 特别是线程安全的:

为了确定数据竞争的存在,内存分配函数的行为就像它们只访问可通过其参数访问的内存位置,而不是其他静态持续时间存储。但是,这些函数可能会明显地修改它们分配或取消分配的存储空间。释放内存区域 p 的 free 或 realloc 调用与分配区域 p 的全部或部分的任何分配调用同步。此同步发生在解除分配函数对 p 的任何访问之后,并且在分配函数进行任何此类访问之前。

对比它在7.24.5.8p6中关于 strtok 的说法,它不是也从来不是线程安全的:

不需要 strtok 函数来避免与对 strtok 函数的其他调用的数据争用。

[脚注:可以使用 strtok_s 函数来避免数据竞争。]

(脚注注释:不要使用strtok_s,使用strsep。)

旧版本的 C 标准对线程安全只字未提。然而,他们确实谈到了可重入性,因为信号一直是 C 标准的一部分。这就是他们所说的,回到最初的1989 年 ANSI C 标准(该文件的措辞与次年发布的 ISO C 标准几乎相同,但部分编号非常不同):

如果 [a] 信号不是由于调用 abort 或 raise 函数而出现的,则如果信号处理程序调用标准库中的任何函数而不是信号函数本身或引用具有静态存储持续时间的任何对象,则行为未定义而不是通过为 volatile sig_atomic_t 类型的静态存储持续时间变量赋值。此外,如果对信号函数的这种调用导致 SIG_ERR 返回,则 errno 的值是不确定的。

这是一种冗长的说法,即作为一般规则,C 库函数不需要可重入非常相似的措辞仍然出现在 C11, 7.14.1.1p5 中

如果 [a] 信号不是由于调用 abort 或 raise 函数而出现的,则如果信号处理程序引用具有静态或线程存储持续时间的任何对象,该对象不是无锁原子对象,而不是通过分配声明为 volatile sig_atomic_t 的对象的值,或信号处理程序调用标准库中除 abort 函数、_Exit 函数、quick_exit 函数或第一个参数等于对应于信号编号的信号函数之外的任何函数导致调用处理程序的信号。此外,如果对信号函数的这种调用导致 SIG_ERR 返回,则 errno 的值是不确定的。

[脚注:如果任何信号由异步信号处理程序生成,则行为未定义。]

与 C 库的整体大小相比, POSIX 需要更长但仍然很短的函数列表,这些函数可以从“异步信号处理程序”安全调用,并且还更详细地定义了信号可能“发生其他情况”的情况而不是调用 abort 或 raise 函数的结果。” 如果您正在使用信号做任何重要的事情,那么您可能正在编写旨在在具有 Unix 特性的操作系统上运行的代码(与 Windows、MVS 或可能没有完整托管的 C 实现的嵌入式操作系统相反)首先),并且您应该熟悉它们的 POSIX 要求以及 ISO C 要求。

于 2018-08-13T19:32:19.077 回答
2

我建议阅读

§31.1 线程安全(以及重新访问)

在The Linux Programming Interface一书中,它解释了线程安全和可重入之间的区别,以及malloc.

摘录:</p>

如果一个函数可以被多个线程同时安全地调用,则称该函数是线程安全的;反过来说,如果一个函数不是线程安全的,那么当它在另一个线程中执行时,我们就不能从一个线程调用它。

....
这个函数说明了一个函数不是线程安全的典型原因:它使用了所有线程共享的全局或静态变量。

...
虽然使用临界区来实现线程安全是对使用每个功能互斥锁的显着改进,但它仍然有些低效,因为锁定和解锁互斥锁是有成本的。可重入函数无需使用互斥体即可实现线程安全。它通过避免使用全局变量和静态变量来做到这一点。

...
但是,并非所有功能都可以重入。通常的原因如下:

  • 就其本质而言,某些函数必须访问全局数据结构。malloc 库中的函数就是一个很好的例子。这些函数维护堆上空闲块的全局链表。通过使用互斥体,库的功能malloc是线程安全的。

……

绝对值得一读。

并回答您的问题,malloc线程安全但不可重入。

于 2019-12-26T01:27:17.467 回答
1

这取决于您使用的 C 运行时库的实现。例如,如果您使用 MSVC,则有一个编译器选项可让您指定要使用哪个版本的库构建(即,通过是否安全来支持多线程的运行时库)。

于 2009-05-13T02:32:34.093 回答
1

不,它不是线程安全的。实际上,您的 C 库中可能有一个可用的malloc_lock()and函数。malloc_unlock()我知道这些存在于 Newlib 库中。我不得不使用它为我的处理器实现互斥锁,该处理器在硬件中是多线程的。

于 2009-05-13T03:02:28.417 回答
1

malloc 和 free 不可重入,因为它们使用静态数据结构来记录哪些内存块是空闲的。因此,没有分配或释放内存的库函数是可重入的。

于 2016-10-05T07:09:10.563 回答
0

不它不是。

Web存档链接(原始已失效)

于 2009-05-13T02:25:09.647 回答