Programming Rust, 2nd Edition 书中的这句话让我有点困惑,
<块引用>... Rust 标准库使用互斥锁保护 stdin
。 (如果没有互斥量,两个线程同时尝试从 stdin
读取会导致未定义的行为。C 有同样的问题并以同样的方式解决它:所有 C 标准输入和输出函数都获得一个在幕后锁定。唯一的区别是在 Rust 中,锁定是 API 的一部分。)
如果 C 中的两个线程使用 stdio.h
是否有处理文件句柄争用的幕后“互斥锁”?我一直认为这是你必须在 C 中明确做的事情,而不是为你做的事情。此外,如果您编译一个单线程 C 应用程序,这些 stdio 的行为是否会神奇地改变并优化掉互斥锁?
答案 0 :(得分:2)
如果 C 中的两个线程使用 stdio.h 是否有任何处理文件句柄争用的幕后“互斥锁”?
C 2018 7.21 7 说:
<块引用>每个流都有一个关联的锁,用于在多个执行线程访问一个流时防止数据竞争,并限制多个线程执行的流操作的交错。一次只能有一个线程持有这个锁。锁是可重入的:单个线程可以在给定时间多次持有锁。
此外,如果您编译一个单线程 C 应用程序,这些 stdio 的行为是否会神奇地改变并优化掉互斥锁?
C 标准允许 C 实现这样做,因为 5.1.2.3 6 说 C 实现只需要产生 可观察的行为,该行为是由按照标准中指定的行为的程序产生的,而不是它必须以标准中描述的方式实施程序。我不知道是否有任何 C 实现这样做。由于使用 <stdio.h>
的模块可以与创建线程然后调用前一个模块的模块分开编译,除非用户要求(可能通过命令行开关或 {{ 1}} 指令)。它必须在链接时(可能通过在标准库的单线程版本中进行链接)或运行时(可能通过在产生线程之前不使用任何锁)来完成。
答案 1 :(得分:1)
在 C11(2011 年发布)之前的 ISO C 标准版本中,没有多线程执行的概念。线程仅作为平台特定的扩展被个别平台支持。因此,由各个平台决定如何支持多线程以及 C 库是否是线程安全的。
例如,Microsoft Windows 平台提供了两个版本的 C 库:它允许您链接一个线程安全的库版本,一个不是线程安全的。该库的非线程安全版本适用于单线程应用程序,具有更好的性能,因为它不需要执行任何线程同步(即没有互斥锁)。
但是,由于 C11 引入了多线程执行的概念,标准要求允许多个线程同时写入同一个流。这意味着 C 库在这方面必须是线程安全的。这需要某种形式的线程同步。互斥体通常用于此目的。
<块引用>此外,如果您编译一个单线程 C 应用程序,这些 stdio 的行为是否会神奇地改变并优化掉互斥锁?
我怀疑编译器是否有可能可靠地检测应用程序是单线程还是多线程。在评论部分,建议在不使用 -lpthread
编译器选项时执行某些链接器优化。但是,这可能只会影响 POSIX Threads 而不会影响 ISO C11 threads。
答案 2 :(得分:0)
我相信这是在 POSIX 中指定的,而不是在 C 标准中指定的,但您可以在这里看到它
您可以在 2017 POSIX Standrad
中看到这一点 <块引用>将标准名称(例如 getc()
、putc()
等)映射到“更快但不安全”而不是“较慢但安全”版本是错误的. 在任何一种情况下,您仍然希望在转换现有代码时手动检查 getc()
、putc()
等的所有使用。选择安全绑定作为默认值,至少会导致正确的代码并保持“函数的原子性”不变性。否则会在转换后的代码中引入无缘无故的同步错误。修改 stdio (FILE *)
结构或缓冲区的其他例程也会安全同步。
在 Debian 中详细说明
man 3 stdio
man 3 unlocked_stdio
一个重要的旁白是 glibc
甚至比 POSIX 标准更进一步,而且其中许多功能都是非标准化的。