为什么malloc()和printf()被称为不可重入?

为什么malloc()和printf()被称为不可重入?

这里至少有三个概念,所有这些概念都被混入口语化的语言中,这可能是您感到困惑的原因。

thread-safecritical sectionre-entrant

先看最简单的: printf 和 都是。自2011年以来,它们在标准C中一直被保证是线程安全的,从2001年开始在POSIX中被保证是线程安全的,在此之前很久就在实践中得到了保证。这意味着下面的程序保证不会崩溃或表现出不良行为:

代码语言:javascript复制#include

#include

void *printme(void *msg) {

while (1)

printf("%s\r", (char*)msg);

}

int main() {

pthread_t thr;

pthread_create(&thr, NULL, printme, "hello");

pthread_create(&thr, NULL, printme, "goodbye");

pthread_join(thr, NULL);

}不是线程安全的函数的一个例子是strtok。如果同时从两个不同的线程调用strtok,结果将是未定义的行为-因为strtok内部使用静态缓冲区来跟踪其状态。glibc添加了strtok_r来解决这个问题,C11添加了与strtok_s相同的东西(但可以选择使用不同的名称,因为不是在这里发明的)。

好吧,但是printf不是也使用全局资源来构建它的输出吗?实际上,同时从两个线程打印到stdout意味着什么呢?这就引出了下一个话题。显然,printf 在任何使用它的程序中都将是一个 。一次只允许在临界区内有一个执行线程。

至少在符合POSIX的系统中,这是通过让printf以对flockfile(stdout)的调用开始并以对funlockfile(stdout)的调用结束来实现的,这基本上类似于获取与标准输出相关联的全局互斥。

但是,程序中的每个不同的FILE都允许有自己的互斥锁。这意味着一个线程可以同时调用fprintf(f1,...),而另一个线程正在调用fprintf(f2,...)。这里没有竞争条件。(您的libc是否真的并行运行这两个调用是一个QoI问题。我实际上不知道glibc是做什么的。)

类似地,在任何现代系统中,malloc都不太可能是一个关键部分,因为现代系统是smart enough to keep one pool of memory for each thread in the system的,而不是让所有N个线程争夺一个池。( sbrk系统调用可能仍然是一个关键部分,但malloc在sbrk中花费的时间很少。或者mmap,或者现在酷孩子们正在使用的任何东西。)

好了,那么 到底是什么意思呢?基本上,它意味着可以安全地递归调用函数--当前调用被“搁置”,而第二个调用正在运行,然后第一个调用仍然能够“从它停止的地方继续”。(从技术上讲,这可能不是由于递归调用造成的:第一次调用可能是在线程A中,中间被线程B中断,线程B进行第二次调用。但这种情况只是线程安全的一种特殊情况,所以我们可以在这一段中忘记它。)

printf和malloc都不可能由单个线程递归调用,因为它们都是叶函数(它们既不调用自己,也不调用任何可能进行递归调用的用户控制的代码)。而且,正如我们在上面看到的,它们从2001年起就对*多线程可重入调用提供了线程安全(通过使用锁)。

因此,无论是谁告诉您printf和malloc是不可重入的,都是错误的;他们的意思可能是,它们都有可能成为程序中的临界区-一次只能通过一个线程的瓶颈。

Pedantic注意: glibc确实提供了一个扩展,通过该扩展,printf可以调用任意用户代码,包括重新调用自身。这在所有的排列中都是完全安全的--至少就线程安全而言是这样的。(很明显,这会导致非常疯狂的格式字符串漏洞。)有两种变体:register_printf_function (有文档记录,相当合理,但正式“不推荐使用”)和register_printf_specifier (除了一个额外的无文档参数和一个total lack of user-facing documentation外,几乎相同)。我不推荐他们中的任何一个,在这里提到他们只是作为一个有趣的旁白。

代码语言:javascript复制#include

#include // glibc extension

int widget(FILE *fp, const struct printf_info *info, const void *const *args) {

static int count = 5;

int w = *((const int *) args[0]);

printf("boo!"); // direct recursive call

return fprintf(fp, --count ? "<%W>" : "<%d>", w); // indirect recursive call

}

int widget_arginfo(const struct printf_info *info, size_t n, int *argtypes) {

argtypes[0] = PA_INT;

return 1;

}

int main() {

register_printf_function('W', widget, widget_arginfo);

printf("|%W|\n", 42);

}

相关推荐