在C语言中,什么是重入和可重入?

7
我正在阅读一本名为《Linux系统编程》的书。引自这本书中的一段话:
“那么系统调用和其他库函数呢? 如果您的进程正在写入文件或分配内存,并且信号处理程序写入同一文件或也调用malloc()怎么办?有些函数显然不是可重入的。如果一个程序正在执行不可重入函数,并发生信号,信号处理程序然后调用相同的不可重入函数,可能会导致混乱。”
但接下来它会跟随:
“保证可重入函数” “针对在信号中安全地重新输入使用的函数” “这里有一些函数..” “write()” “这里有一些函数..”
我很困惑,write()是否是可重入的?因为我认为这与以下声明冲突:
“如果您的进程正在写入文件时怎么办?”
4个回答

7

如果可以从不同的上下文中调用函数而不会干扰另一个上下文中的调用,那么可重入性就更具有实际意义。

例如,考虑 strtok 函数。它通常包含一个静态局部变量,以跟踪您正在分词的字符串中的下一个位置。由于局部静态变量在所有调用该函数的情况下都是共享的,从两个不同的上下文调用该函数将导致问题。

另一方面,write 系统调用没有存储在调用之间的内部数据,因此可以安全地从不同的上下文调用。


需要注意的是,可重入与线程安全不是相同的概念。以 write 函数为例,由于它是可重入的,因此可以使用不同的文件从不同的线程调用它,而不必担心内部数据会被破坏。但是,它并不是线程安全的。在使用相同的文件描述符从不同的线程调用它将导致问题。


4
“re-entrant”是否也意味着“线程安全”?我认为不是!(我认为重要的是对此进行说明) - Sir Jo Black
请解释一下“上下文”这个术语的含义。 - Nikolai Ruhe
@SergioFormiggini 没错,我会添加一条注释。 - Some programmer dude
先生,我刚刚将您的观点作为答案进行了详细阐述,因为我认为在评论中写太多会显得过于冗长。请您能否再次审查一下? - Sourav Ghosh

7

补充一下@Joachim Pileborg在他的回答中提到的,根据关于可重入性的维基词条,函数需要满足以下基本规则才能被称为可重入:

  1. 可重入代码不得持有任何静态(或全局)非常量数据。
  2. 可重入代码不得修改其自身的代码。
  3. 可重入代码不得调用不可重入的计算机程序或子程序。

更具体地说,如果函数是可重入的,那么它的实现(包括其内部使用的数据结构)将不受从不同上下文中调用的影响。

对于提供给函数的参数(例如文件描述符),并不会影响其可重入性。

因此,对于write()函数本身是可重入的,但如果从不同的线程中使用相同的文件描述符调用它,显然会产生错误的结果。再次强调,这并不意味着write()的可重入性消失了。它仍然是可重入的,但不是线程安全的,这两个概念是不同的。


write 在当前的 Linux 内核中是线程安全的。详情请参见我的回答 - 4566976

2

你引用的文档是指 信号处理函数。这是一种非常特殊的函数类型,只有在出现异常情况时才会被调用,应该被视为特定的系统编程。它们违反了程序的正常控制流。

如果你没有编写信号处理函数,那么这份文档对你来说并没有太大用处。不过,以下是在 Mac OS 上信号安全(signal safe)的函数列表:

$ man sigaction

The following functions are either reentrant or not interruptible by
signals and are async-signal safe.  Therefore applications may invoke
them, without restriction, from signal-catching functions:

Base Interfaces:

_exit(), access(), alarm(), cfgetispeed(), cfgetospeed(),
cfsetispeed(), cfsetospeed(), chdir(), chmod(), chown(), close(),
creat(), dup(), dup2(), execle(), execve(), fcntl(), fork(),
fpathconf(), fstat(), fsync(), getegid(), geteuid(), getgid(),
getgroups(), getpgrp(), getpid(), getppid(), getuid(), kill(),
link(), lseek(), mkdir(), mkfifo(), open(), pathconf(), pause(),
pipe(), raise(), read(), rename(), rmdir(), setgid(), setpgid(),
setsid(), setuid(), sigaction(), sigaddset(), sigdelset(),
sigemptyset(), sigfillset(), sigismember(), signal(), sigpending(),
sigprocmask(), sigsuspend(), sleep(), stat(), sysconf(), tcdrain(),
tcflow(), tcflush(), tcgetattr(), tcgetpgrp(), tcsendbreak(),
tcsetattr(), tcsetpgrp(), time(), times(), umask(), uname(),
unlink(), utime(), wait(), waitpid(), write().

2
Sourav Ghosh和Joachim Pileborg的答案似乎与write的线程安全性不正确:
根据POSIX.1-2008,write应该是线程安全的,因为它没有出现在此列表中。
然而,在glibc wiki上:
目前Linux的write系统调用不是MT-safe。多个线程竞争写入可能会获得相同的文件位置值并写入相同的位置,导致数据丢失。
看起来这个问题已经在Linux内核中得到解决(请参见linux kernel mailing list)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接