在Unix系统中,我们知道 malloc() 是一个非诱因函数(系统调用)。这是为什么?

相似地, printf() 据说也是非伦特的;为什么?

我知道重新进入的定义,但我想知道为什么它适用于这些功能。是什么防止他们保证重新进入?

有帮助吗?

解决方案

mallocprintf 通常使用全球结构,并在内部采用基于锁定的同步。这就是为什么他们不再进入。

malloc 函数可以是线程安全或线程安全性。两者都不是重进入的:

  1. Malloc在全球堆上运行,并且可能有两个不同的调用 malloc 同时发生这种情况,返回相同的内存块。 (第二次malloc呼叫应该在获取块的地址之前进行,但没有标记为不可用)。这违反了 malloc, ,因此该实施不会重新加入。

  2. 为了防止这种效果,线程安全的实现 malloc 将使用基于锁定的同步。但是,如果从信号处理程序中调用malloc,则可能发生以下情况:

    malloc();            //initial call
      lock(memory_lock); //acquire lock inside malloc implementation
    signal_handler();    //interrupt and process signal
    malloc();            //call malloc() inside signal handler
      lock(memory_lock); //try to acquire lock in malloc implementation
      // DEADLOCK!  We wait for release of memory_lock, but 
      // it won't be released because the original malloc call is interrupted
    

    这种情况不会发生 malloc 简单地从不同的线程调用。确实,重新输入概念超出了线程安全性,还需要功能才能正常工作 即使其中一个援引从未终止. 。这基本上就是为什么任何具有锁的功能都不会重新输入的原因。

printf 功能还可以在全局数据上运行。任何输出流通常使用附加到资源数据的全局缓冲区发送到(终端或文件缓冲区)。打印过程通常是将数据复制到缓冲区并以后冲洗缓冲区的顺序。该缓冲区应以相同的方式受到锁的保护 malloc 做。所以, printf 也是非诱因。

其他提示

让我们了解我们的意思 重点. 。可以在先前的调用完成之前调用重点函数。如果这可能会发生

  • 信号处理程序(或比Unix某些中断处理程序)在执行过程中升高的信号中调用函数
  • 函数被递归称为

Malloc不是重新进入的,因为它正在管理几个跟踪自由存储器块的全局数据结构。

printf不是重新输入的,因为它修改了全局变量,即文件* stout的内容。

这里至少有三个概念,所有这些概念都用口语混为一谈,这可能就是为什么您感到困惑。

  • 线程安全
  • 关键部分
  • 重点

首先采取最简单的一个: 两个都 mallocprintf线程安全. 。自2011年以来,自2011年以来,自2001年以来在POSIX以及从那以后的实践中就可以保证它们在标准C中是安全性。这意味着以下程序可以保证不崩溃或表现出不良行为:

#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 也使用全球资源来构建其输出?实际上,甚至会有什么 意思是 从两个线程打印到stdout 同时? 这使我们进入了下一个主题。明显地 printf 会成为一个 关键部分 在任何使用它的程序中。 一次只允许执行一个线程。

至少在符合POSIX的系统中,这是通过拥有 printf 从打电话开始 flockfile(stdout) 并以打电话结尾 funlockfile(stdout), ,这基本上就像服用与Stdout相关的全局静音。

但是,每个都不同 FILE 在程序中,允许拥有自己的静音。这意味着一个线程可以调用 fprintf(f1,...) 同时第二个线程在呼叫的中间 fprintf(f2,...). 。这里没有种族条件。 (您的libc是否真的在并行运行这两个电话是 QOI 问题。我实际上不知道Glibc做什么。)

相似地, malloc 在任何现代系统中都不太可能成为关键部分,因为现代系统是 足够聪明,可以为系统中的每个线程保留一个内存池, ,而不是让所有n个线程在一个池上战斗。 (这 sbrk 系统呼叫仍然可能是关键部分,但是 malloc 花很少的时间在 sbrk. 。或者 mmap, ,或这些天很酷的孩子正在使用的任何东西。)

可以,然后呢 有什么 重新进入 实际上是什么意思? 基本上,这意味着该函数可以安全地递归地称为 - 当前调用是“搁置”的,而第二次调用则运行,然后第一个调用仍然可以“拾取其关闭的位置”。 (从技术上讲 可能 不是由于递归调用:第一个调用可能在线程A中,该线程A中间被线B中间打断,这是第二个调用。但是这种情况只是 线程安全, ,因此我们可以在本段中忘记它。)

两者都不 printf 也不 malloc 可以 由单个线程递归地称为叶子功能(它们不称呼自己,也不称为任何用户控制的代码,这些代码可能会进行递归调用)。而且,正如我们在上面看到的那样,自2001年以来,它们一直是针对 *多 *螺纹重新输入调用的线程安全的(使用锁)。

所以,谁告诉你 printfmalloc 非伦理是错误的;他们的意思是,他们俩都有可能成为 关键部分 在您的程序中 - 瓶颈一次只能通过一个线程。


pedantic注意:GLIBC确实提供了一个扩展名 printf 可以调用任意用户代码,包括重新打电话。这在所有排列中都是完全安全的 - 至少就线程安全而言。 (显然它为绝对打开了大门 疯狂的 格式串联漏洞。)有两个变体: register_printf_function (有记录和合理的理智,但正式“贬低”)和 register_printf_specifier (那是 几乎 除了一个额外的无证件参数和一个 完全缺乏面向用户的文档)。我不推荐他们中的任何一个,而是在这里提及它们。

#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);
}

很可能是因为您无法开始编写输出,而另一个对PrintF的调用仍在打印其自我。内存分配和交易的情况也是如此。

这是因为两者都可以使用全球资源:堆内存结构和控制台。

编辑:堆不过是一种链接列表结构。每个 malloc 或者 free 修改它,因此,在编写访问权限的同一时间内有几个线程会损害其一致性。

EDIT2:另一个细节:默认情况下可以通过使用Mutexes将它们重新进入。但是这种方法是昂贵的,而且没有garanty将始终在MT环境中使用。

因此,有两种解决方案:要制作2个库功能,一个重点和一个库,或将MUTEX零件留给用户。他们选择了第二个。

同样,这可能是因为这些功能的原始版本是非诱因的,因此已被宣布为兼容性。

如果您尝试从两个单独的线程调用malloc(除非您有一个线程安全版本,而不是由C标准保证),则发生坏事,因为两个线程只有一个堆。对于printf来说,行为是不确定的。这就是使他们实际上非属于的原因。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top