这里至少有三个概念,所有这些概念都在口语中混为一谈,这可能就是您感到困惑的原因。
先取最简单的一个:malloc 和 printf 都是 thread-safe。自 2011 年以来,它们在标准 C 中被保证是线程安全的,自 2001 年以来在 POSIX 中被保证是线程安全的,并且在很久以前就在实践中。这意味着保证以下程序不会崩溃或表现出不良行为:
#include <pthread.h>
#include <stdio.h>
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 不也使用全局资源来构建它的输出吗?实际上,意味着同时从两个线程打印到标准输出?这将我们带到下一个主题。显然 printf 在任何使用它的程序中都将成为critical section。 一次只允许一个执行线程进入临界区。
至少在 POSIX 兼容的系统中,这是通过让 printf 以调用 flockfile(stdout) 开始并以调用 funlockfile(stdout) 结束来实现的,这基本上就像采用与 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,或者现在很酷的孩子们正在使用的任何东西。)
好的,那么 re-entrancy 究竟是什么意思? 基本上,这意味着可以安全地递归调用该函数——当前调用在第二次调用运行时被“搁置”,并且那么第一次调用仍然能够“从中断的地方继续”。 (从技术上讲,这可能不是由于递归调用:第一次调用可能在线程 A 中,线程 B 在中间被线程 B 中断,从而进行第二次调用。但这种情况只是一个线程安全的特殊情况,所以我们可以在本段中忘记它。)
printf 和 malloc 都不能被单个线程递归调用,因为它们是叶函数(它们不会调用自己,也不会调用任何用户控制的代码可能会进行递归调用)。而且,正如我们在上面看到的,自 2001 年以来,它们对 *multi-*threaded 重入调用是线程安全的(通过使用锁)。
所以,谁告诉你printf 和malloc 是不可重入的,那就错了;他们的意思可能是它们都有可能成为您程序中的关键部分——一次只能通过一个线程的瓶颈。
迂腐提示:glibc 确实提供了一个扩展,通过它printf 可以调用任意用户代码,包括重新调用自身。这在所有排列中都是完全安全的——至少就线程安全而言。 (显然它为绝对insane 格式字符串漏洞打开了大门。)有两个变体:register_printf_function(已记录在案且相当合理,但正式“弃用”)和register_printf_specifier(其几乎相同,除了一个额外的未记录参数和一个total lack of user-facing documentation)。我不会推荐它们中的任何一个,在这里提及它们只是作为一个有趣的旁白。
#include <stdio.h>
#include <printf.h> // 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);
}