C语言读写和线程安全 (Linux)

14

如果在两个不同的线程上调用相同文件描述符的read(或write,或两者),而没有显式使用同步机制,会发生什么?

读和写是系统调用,在单核 CPU 上,可能不太可能同时执行两次读取。但在多核情况下...

Linux 内核会怎么做?

让我们更加一般化:其他内核(如 BSD)的行为是否始终相同?

编辑:根据close documentation,我们应该确保文件描述符没有被其他线程的系统调用使用。因此,在关闭文件描述符之前(因此,在仍在运行可能调用它的线程周围的读/写也需要显式同步。


2
你不能不使用同步机制;内核已经为你做了这个。 - Carl Norum
1
我的意思是,使用明确的方式。因此,内核自己完成整个工作,对它们的读取/写入/调用都不需要任何显式同步? - Jeremy Cochoy
据我所知,是的。我并不是一个 Linux 内核专家,但我无法想象它会有所不同。也许会有更具体知识的人出现。 - Carl Norum
我不知道会发生什么。我永远不会尝试这样做 - 我让线程打开自己的fd。 - Martin James
1
尝试一下吧:),让我们知道。 - aah134
readwrite(以及readvwritev)引起的文件偏移更新应该是原子性的,但请查看Linux man页面中“BUGS”部分,了解在Linux内核版本3.14中修复的问题。 - Ian Abbott
4个回答

19

在所有主流类Unix操作系统中,任何系统级(syscall)文件描述符访问都是线程安全的。但是根据其版本的不同,它们不一定是信号安全(signal safe)的。

如果您从两个不同的任务调用来自文件描述符的readwriteaccept或类似的函数,则内核的内部锁机制将解决争用。

对于读取操作,每个字节只能被读取一次,而写入操作则可能以任意未定义的顺序进行。

stdio库函数freadfwrite等默认情况下也具有控制结构的内部锁定保护,但可以通过使用标志来禁用该锁定机制。


1
你好。你能否发布一份官方文档,将这些函数标记为线程安全的? - felknight
2
如果您能添加一些关于read/write/accept和fread/fwrite的官方文档链接,我会非常高兴 :) - Jeremy Cochoy
我非常确定,除非您链接libpthread,否则glibc在fread、fwrite上不会是线程安全的。但是,如果您正在使用线程,那么这当然已经是真的了。 - Zan Lynx
1
+1. 在同一个文件描述符上同时调用read/write是完全可以的。实际上,即使在fd仍然被系统调用使用时进行close()操作也是可能的,但这通常是一个不好的主意。人们似乎唯一需要这样做的原因是通过从另一个线程关闭fd来中断阻塞的系统调用,但我必须指出这种行为是严格来说“非可移植”的(我需要查找适用于哪些系统)。因此,文档上建议不要这样做是正确的:尽管内核会确保您不会导致系统崩溃,但您的应用程序并不会高兴。 - Nicholas Wilson
2
请注意,如果您在一个线程中调用write函数,而另一个线程正在修改传递给内核的缓冲区,则实际写入的数据可能包含其他线程正在放置的一些修改数据,也可能不包含。 - Chris Dodd

5

关于close的评论是因为在任何其他线程尝试使用文件描述符时关闭文件描述符都没有太多意义。 因此,虽然从内核的角度来看它是“安全”的,但它可能会导致奇怪、难以诊断的边角情况。

如果一个线程在第二个线程尝试从中读取时关闭一个文件描述符,则第二个线程可能会收到意外的EBADF错误。 更糟糕的是,如果第三个线程正在同时打开一个新文件,那么可能会重新分配相同的fd,并且第二个线程可能会意外地从新文件中读取而不是它预期的文件...


2

关注那些跟随你脚步的人

在保护文件描述符时使用互斥信号量是非常正常的。这样可以消除对内核行为的依赖,因此你现在可以确定消息边界了。这样你就不必引用一个有15,489行(我夸张了,但你明白我的意思)的手册底部的最后一段解释为什么不需要互斥信号量。

同时,这也清楚地表明文件描述符正在被多个线程使用,方便其他人阅读你的代码。

附加好处

使用互斥锁的另一个好处是,如果来自不同线程的消息优先级不同,你只需要设置线程的优先级以反映其消息的重要性,操作系统会确保按照重要性的顺序发送消息,而你所需做的工作很少。


0

结果取决于线程在特定时间点如何被调度运行。

避免多线程的未定义行为的一种方法是假设您正在进行内存操作。例如,更新链接列表或更改变量等。

如果使用互斥/信号量/锁或其他同步机制,则应按预期工作。


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