C 是否在幕后使用互斥锁处理 STDIN?

时间:2021-08-01 20:28:06

标签: c multithreading input io mutex

Programming Rust, 2nd Edition 书中的这句话让我有点困惑,

<块引用>

... Rust 标准库使用互斥锁保护 stdin。 (如果没有互斥量,两个线程同时尝试从 stdin 读取会导致未定义的行为。C 有同样的问题并以同样的方式解决它:所有 C 标准输入和输出函数都获得一个在幕后锁定。唯一的区别是在 Rust 中,锁定是 API 的一部分。)

如果 C 中的两个线程使用 stdio.h 是否有处理文件句柄争用的幕后“互斥锁”?我一直认为这是你必须在 C 中明确做的事情,而不是为你做的事情。此外,如果您编译一个单线程 C 应用程序,这些 stdio 的行为是否会神奇地改变并优化掉互斥锁?

3 个答案:

答案 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 中详细说明