如果在两个不同的线程上调用相同文件描述符的read
(或write
,或两者),而没有显式使用同步机制,会发生什么?
读和写是系统调用,在单核 CPU 上,可能不太可能同时执行两次读取。但在多核情况下...
Linux 内核会怎么做?
让我们更加一般化:其他内核(如 BSD)的行为是否始终相同?
编辑:根据close documentation,我们应该确保文件描述符没有被其他线程的系统调用使用。因此,在关闭文件描述符之前(因此,在仍在运行可能调用它的线程周围的读/写也需要显式同步。
如果在两个不同的线程上调用相同文件描述符的read
(或write
,或两者),而没有显式使用同步机制,会发生什么?
读和写是系统调用,在单核 CPU 上,可能不太可能同时执行两次读取。但在多核情况下...
Linux 内核会怎么做?
让我们更加一般化:其他内核(如 BSD)的行为是否始终相同?
编辑:根据close documentation,我们应该确保文件描述符没有被其他线程的系统调用使用。因此,在关闭文件描述符之前(因此,在仍在运行可能调用它的线程周围的读/写也需要显式同步。
在所有主流类Unix操作系统中,任何系统级(syscall)文件描述符访问都是线程安全的。但是根据其版本的不同,它们不一定是信号安全(signal safe)的。
如果您从两个不同的任务调用来自文件描述符的read
、write
、accept
或类似的函数,则内核的内部锁机制将解决争用。
对于读取操作,每个字节只能被读取一次,而写入操作则可能以任意未定义的顺序进行。
stdio库函数fread
、fwrite
等默认情况下也具有控制结构的内部锁定保护,但可以通过使用标志来禁用该锁定机制。
关于close的评论是因为在任何其他线程尝试使用文件描述符时关闭文件描述符都没有太多意义。 因此,虽然从内核的角度来看它是“安全”的,但它可能会导致奇怪、难以诊断的边角情况。
如果一个线程在第二个线程尝试从中读取时关闭一个文件描述符,则第二个线程可能会收到意外的EBADF错误。 更糟糕的是,如果第三个线程正在同时打开一个新文件,那么可能会重新分配相同的fd,并且第二个线程可能会意外地从新文件中读取而不是它预期的文件...
关注那些跟随你脚步的人
在保护文件描述符时使用互斥信号量是非常正常的。这样可以消除对内核行为的依赖,因此你现在可以确定消息边界了。这样你就不必引用一个有15,489行(我夸张了,但你明白我的意思)的手册底部的最后一段解释为什么不需要互斥信号量。
同时,这也清楚地表明文件描述符正在被多个线程使用,方便其他人阅读你的代码。
附加好处
使用互斥锁的另一个好处是,如果来自不同线程的消息优先级不同,你只需要设置线程的优先级以反映其消息的重要性,操作系统会确保按照重要性的顺序发送消息,而你所需做的工作很少。
结果取决于线程在特定时间点如何被调度运行。
避免多线程的未定义行为的一种方法是假设您正在进行内存操作。例如,更新链接列表或更改变量等。
如果使用互斥/信号量/锁或其他同步机制,则应按预期工作。
read
和write
(以及readv
和writev
)引起的文件偏移更新应该是原子性的,但请查看Linux man页面中“BUGS”部分,了解在Linux内核版本3.14中修复的问题。 - Ian Abbott