在Linux中,stdout线程安全?

时间:2022-04-07 21:00:12

Is writing to stdout using printf thread-safe on Linux? What about using the lower-level write command?

在Linux上使用printf线程安全写入stdout吗?使用低级别的写命令怎么样?

5 个解决方案

#1


47  

It's not specified by the C standard -- it depends on your implementation of the C standard library. In fact, the C standard doesn't even mention threads at all, since certain systems (e.g. embedded systems) don't have multithreading.

它不是由C标准指定的——它取决于您对C标准库的实现。事实上,C标准甚至没有提到线程,因为某些系统(例如嵌入式系统)没有多线程。

In the GNU implementation (glibc), most of the higher-level functions in stdio that deal with FILE* objects are thread-safe. The ones that aren't usually have unlocked in their names (e.g. getc_unlocked(3)). However, the thread safety is at a per-function call level: if you make multiple calls to printf(3), for example, each of those calls is guaranteed to output atomically, but other threads might print things out between your calls to printf(). If you want to ensure that a sequence of I/O calls gets output atomically, you can surround them with a pair of flockfile(3)/funlockfile(3) calls to lock the FILE handle. Note that these functions are reentrant, so you can safely call printf() in between them, and that won't result in deadlock even thought printf() itself makes a call to flockfile().

在GNU实现(glibc)中,处理FILE*对象的stdio中的大多数高级函数都是线程安全的。那些名字中通常没有解锁的(例如getc_unlock(3))。但是,线程安全处于每个函数调用级别:例如,如果对printf(3)进行多次调用,那么每个调用都保证以原子方式输出,但是其他线程可能会在对printf()的调用之间输出内容。如果您希望确保一个I/O调用序列以原子方式获得输出,您可以使用一对flockfile(3)/funlockfile(3)调用来锁定文件句柄。注意,这些函数是可重入的,所以您可以安全地在它们之间调用printf(),即使认为printf()本身调用flockfile()也不会导致死锁。

The low-level I/O calls such as write(2) should be thread-safe, but I'm not 100% sure of that - write() makes a system call into the kernel to perform I/O. How exactly this happens depends on what kernel you're using. It might be the sysenter instruction, or the int (interrupt) instruction on older systems. Once inside the kernel, it's up to the kernel to make sure that the I/O is thread-safe. In a test I just did with the Darwin Kernel Version 8.11.1, write(2) appears to be thread-safe.

低级的I/O调用(如write(2))应该是线程安全的,但我不能100%确定这一点——write()对内核进行系统调用以执行I/O。这具体是如何发生的,取决于您使用的内核。它可能是sysenter指令,或者是旧系统上的int(中断)指令。一旦进入内核,就由内核来确保I/O是线程安全的。在我刚刚对Darwin内核8.11.1版本进行的测试中,write(2)似乎是线程安全的。

#2


20  

Whether you'd call it "thread-safe" depends on your definition of thread-safe. POSIX requires stdio functions to use locking, so your program will not crash, corrupt the FILE object states, etc. if you use printf simultaneously from multiple threads. However, all stdio operations are formally specified in terms of repeated calls to fgetc and fputc, so there is no larger-scale atomicity guaranteed. That is to say, if threads 1 and 2 try to print "Hello\n" and "Goodbye\n" at the same time, there's no guarantee that the output will be either "Hello\nGoodbye\n" or "Goodbye\nHello\n". It could just as well be "HGelolodboy\ne\n". In practice, most implementations will acquire a single lock for the entire higher-level write call simply because it's more efficient, but your program should not assume so. There may be corner cases where this is not done; for instance an implementation could probably entirely omit locking on unbuffered streams.

您是否将它称为“线程安全”取决于您对线程安全的定义。POSIX要求stdio函数使用锁定,因此如果您同时从多个线程使用printf,您的程序将不会崩溃、损坏文件对象状态等。但是,所有stdio操作都是根据对fgetc和fputc的重复调用而正式指定的,因此不能保证更大的原子性。也就是说,如果线程1和线程2同时尝试打印“Hello\n”和“Goodbye\n”,则不能保证输出是“Hello\nGoodbye\n”或“Goodbye\nHello\ nHello\n”。它也可以是“HGelolodboy\ne\n”。在实践中,大多数实现都会为整个高级的写调用获取一个锁,这仅仅是因为它更有效,但是您的程序不应该这样假设。在某些情况下,这是不可能的;例如,实现可能会完全忽略对未缓冲流的锁定。

Edit: The above text about atomicity is incorrect. POSIX guarantees all stdio operations are atomic, but the guarantee is hidden in the documentation for flockfile: http://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html

编辑:上面关于原子性的文字是不正确的。POSIX保证所有的stdio操作都是原子的,但是该保证隐藏在flockfile的文档中:http://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html。

All functions that reference ( FILE *) objects shall behave as if they use flockfile() and funlockfile() internally to obtain ownership of these ( FILE *) objects.

引用(FILE *)对象的所有函数都应该像使用flockfile()和funlockfile()一样,以获得这些(文件*)对象的所有权。

You can use the flockfile, ftrylockfile, and funlockfile functions yourself to achieve larger-than-single-function-call atomic writes.

您可以自己使用flockfile、ftrylockfile和funlockfile函数来实现比单个函数更大的调用原子写入。

#3


12  

They are both thread-safe to the point that your application won't crash if multiple threads call them on the same file descriptor. However, without some application-level locking, whatever is written could be interleaved.

它们都是线程安全的,如果多个线程在相同的文件描述符上调用它们,则应用程序不会崩溃。但是,如果没有应用程序级的锁定,所写的内容都可以交叉。

#4


4  

It's thread-safe; printf should be reentrant, and you won't cause any strangeness or corruption in your program.

这是线程安全的;printf应该是可重入的,在您的程序中不会引起任何奇怪或损坏。

You can't guarantee that your output from one thread won't start half way through the output from another thread. If you care about that you need to develop your own locked output code to prevent multiple access.

您不能保证一个线程的输出不会从另一个线程的输出开始到一半。如果您关心这个问题,您需要开发自己的锁定输出代码,以防止多次访问。

#5


3  

C got a new standard since this question was asked (and last answered).

自从这个问题被提出(最后回答)后,C得到了一个新的标准。

C11 now comes with multithreading support and addresses multithreaded behavior of streams:

C11现在提供了多线程支持,并解决了流的多线程行为:

§7.21.2 Streams

§7.21.2流

¶7 Each stream has an associated lock that is used to prevent data races when multiple threads of execution access a stream, and to restrict the interleaving of stream operations performed by multiple threads. Only one thread may hold this lock at a time. The lock is reentrant: a single thread may hold the lock multiple times at a given time.

¶7每个流都有一个关联的锁是用来防止数据竞争当执行多个线程访问一个流,并限制交叉流由多个线程执行操作。每次只能有一个线程持有此锁。锁是可重入的:单个线程可以在给定的时间内多次持有锁。

¶8 All functions that read, write, position, or query the position of a stream lock the stream before accessing it. They release the lock associated with the stream when the access is complete.

¶8所有功能,读、写的位置,或查询流锁定流之前访问的位置。当访问完成时,它们释放与流关联的锁。

So, an implementation with C11 threads must guarantee that using printf is thread-safe.

因此,使用C11线程的实现必须保证使用printf是线程安全的。

Whether atomicity (as in no interleaving1) is guaranteed, wasn't that clear to me at a first glance, because the standard spoke of restricting interleaving, as opposed to preventing, which it mandated for data races.

原子性(在no interleaving1中)是否得到保证,我第一眼就看不太清楚,因为标准谈到了限制交叉,而不是防止,它要求数据竞争。

I lean towards it being guaranteed. The standard speaks of restricting interleaving, as some interleaving that doesn't change the outcome is still allowed to happen; e.g. fwrite some bytes, fseek back some more and fwrite till the original offset, so that both fwrites are back-to-back. The implementation is free to reorder these 2 fwrites and merge them into a single write.

我倾向于得到保证。标准说的是限制交错,一些不会改变结果的交错仍然是允许的;例如:fwrite一些字节,fseek back一些字节,fwrite直到原始的偏移量,这样两个fwrite都是背对背的。实现可以*地重新排序这两个fwrite,并将它们合并到一个写入中。


1: See the strike-through text in R..'s answer for an example.

1:在R.. .看文章。这是一个例子。

#1


47  

It's not specified by the C standard -- it depends on your implementation of the C standard library. In fact, the C standard doesn't even mention threads at all, since certain systems (e.g. embedded systems) don't have multithreading.

它不是由C标准指定的——它取决于您对C标准库的实现。事实上,C标准甚至没有提到线程,因为某些系统(例如嵌入式系统)没有多线程。

In the GNU implementation (glibc), most of the higher-level functions in stdio that deal with FILE* objects are thread-safe. The ones that aren't usually have unlocked in their names (e.g. getc_unlocked(3)). However, the thread safety is at a per-function call level: if you make multiple calls to printf(3), for example, each of those calls is guaranteed to output atomically, but other threads might print things out between your calls to printf(). If you want to ensure that a sequence of I/O calls gets output atomically, you can surround them with a pair of flockfile(3)/funlockfile(3) calls to lock the FILE handle. Note that these functions are reentrant, so you can safely call printf() in between them, and that won't result in deadlock even thought printf() itself makes a call to flockfile().

在GNU实现(glibc)中,处理FILE*对象的stdio中的大多数高级函数都是线程安全的。那些名字中通常没有解锁的(例如getc_unlock(3))。但是,线程安全处于每个函数调用级别:例如,如果对printf(3)进行多次调用,那么每个调用都保证以原子方式输出,但是其他线程可能会在对printf()的调用之间输出内容。如果您希望确保一个I/O调用序列以原子方式获得输出,您可以使用一对flockfile(3)/funlockfile(3)调用来锁定文件句柄。注意,这些函数是可重入的,所以您可以安全地在它们之间调用printf(),即使认为printf()本身调用flockfile()也不会导致死锁。

The low-level I/O calls such as write(2) should be thread-safe, but I'm not 100% sure of that - write() makes a system call into the kernel to perform I/O. How exactly this happens depends on what kernel you're using. It might be the sysenter instruction, or the int (interrupt) instruction on older systems. Once inside the kernel, it's up to the kernel to make sure that the I/O is thread-safe. In a test I just did with the Darwin Kernel Version 8.11.1, write(2) appears to be thread-safe.

低级的I/O调用(如write(2))应该是线程安全的,但我不能100%确定这一点——write()对内核进行系统调用以执行I/O。这具体是如何发生的,取决于您使用的内核。它可能是sysenter指令,或者是旧系统上的int(中断)指令。一旦进入内核,就由内核来确保I/O是线程安全的。在我刚刚对Darwin内核8.11.1版本进行的测试中,write(2)似乎是线程安全的。

#2


20  

Whether you'd call it "thread-safe" depends on your definition of thread-safe. POSIX requires stdio functions to use locking, so your program will not crash, corrupt the FILE object states, etc. if you use printf simultaneously from multiple threads. However, all stdio operations are formally specified in terms of repeated calls to fgetc and fputc, so there is no larger-scale atomicity guaranteed. That is to say, if threads 1 and 2 try to print "Hello\n" and "Goodbye\n" at the same time, there's no guarantee that the output will be either "Hello\nGoodbye\n" or "Goodbye\nHello\n". It could just as well be "HGelolodboy\ne\n". In practice, most implementations will acquire a single lock for the entire higher-level write call simply because it's more efficient, but your program should not assume so. There may be corner cases where this is not done; for instance an implementation could probably entirely omit locking on unbuffered streams.

您是否将它称为“线程安全”取决于您对线程安全的定义。POSIX要求stdio函数使用锁定,因此如果您同时从多个线程使用printf,您的程序将不会崩溃、损坏文件对象状态等。但是,所有stdio操作都是根据对fgetc和fputc的重复调用而正式指定的,因此不能保证更大的原子性。也就是说,如果线程1和线程2同时尝试打印“Hello\n”和“Goodbye\n”,则不能保证输出是“Hello\nGoodbye\n”或“Goodbye\nHello\ nHello\n”。它也可以是“HGelolodboy\ne\n”。在实践中,大多数实现都会为整个高级的写调用获取一个锁,这仅仅是因为它更有效,但是您的程序不应该这样假设。在某些情况下,这是不可能的;例如,实现可能会完全忽略对未缓冲流的锁定。

Edit: The above text about atomicity is incorrect. POSIX guarantees all stdio operations are atomic, but the guarantee is hidden in the documentation for flockfile: http://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html

编辑:上面关于原子性的文字是不正确的。POSIX保证所有的stdio操作都是原子的,但是该保证隐藏在flockfile的文档中:http://pubs.opengroup.org/onlinepubs/9699919799/functions/flockfile.html。

All functions that reference ( FILE *) objects shall behave as if they use flockfile() and funlockfile() internally to obtain ownership of these ( FILE *) objects.

引用(FILE *)对象的所有函数都应该像使用flockfile()和funlockfile()一样,以获得这些(文件*)对象的所有权。

You can use the flockfile, ftrylockfile, and funlockfile functions yourself to achieve larger-than-single-function-call atomic writes.

您可以自己使用flockfile、ftrylockfile和funlockfile函数来实现比单个函数更大的调用原子写入。

#3


12  

They are both thread-safe to the point that your application won't crash if multiple threads call them on the same file descriptor. However, without some application-level locking, whatever is written could be interleaved.

它们都是线程安全的,如果多个线程在相同的文件描述符上调用它们,则应用程序不会崩溃。但是,如果没有应用程序级的锁定,所写的内容都可以交叉。

#4


4  

It's thread-safe; printf should be reentrant, and you won't cause any strangeness or corruption in your program.

这是线程安全的;printf应该是可重入的,在您的程序中不会引起任何奇怪或损坏。

You can't guarantee that your output from one thread won't start half way through the output from another thread. If you care about that you need to develop your own locked output code to prevent multiple access.

您不能保证一个线程的输出不会从另一个线程的输出开始到一半。如果您关心这个问题,您需要开发自己的锁定输出代码,以防止多次访问。

#5


3  

C got a new standard since this question was asked (and last answered).

自从这个问题被提出(最后回答)后,C得到了一个新的标准。

C11 now comes with multithreading support and addresses multithreaded behavior of streams:

C11现在提供了多线程支持,并解决了流的多线程行为:

§7.21.2 Streams

§7.21.2流

¶7 Each stream has an associated lock that is used to prevent data races when multiple threads of execution access a stream, and to restrict the interleaving of stream operations performed by multiple threads. Only one thread may hold this lock at a time. The lock is reentrant: a single thread may hold the lock multiple times at a given time.

¶7每个流都有一个关联的锁是用来防止数据竞争当执行多个线程访问一个流,并限制交叉流由多个线程执行操作。每次只能有一个线程持有此锁。锁是可重入的:单个线程可以在给定的时间内多次持有锁。

¶8 All functions that read, write, position, or query the position of a stream lock the stream before accessing it. They release the lock associated with the stream when the access is complete.

¶8所有功能,读、写的位置,或查询流锁定流之前访问的位置。当访问完成时,它们释放与流关联的锁。

So, an implementation with C11 threads must guarantee that using printf is thread-safe.

因此,使用C11线程的实现必须保证使用printf是线程安全的。

Whether atomicity (as in no interleaving1) is guaranteed, wasn't that clear to me at a first glance, because the standard spoke of restricting interleaving, as opposed to preventing, which it mandated for data races.

原子性(在no interleaving1中)是否得到保证,我第一眼就看不太清楚,因为标准谈到了限制交叉,而不是防止,它要求数据竞争。

I lean towards it being guaranteed. The standard speaks of restricting interleaving, as some interleaving that doesn't change the outcome is still allowed to happen; e.g. fwrite some bytes, fseek back some more and fwrite till the original offset, so that both fwrites are back-to-back. The implementation is free to reorder these 2 fwrites and merge them into a single write.

我倾向于得到保证。标准说的是限制交错,一些不会改变结果的交错仍然是允许的;例如:fwrite一些字节,fseek back一些字节,fwrite直到原始的偏移量,这样两个fwrite都是背对背的。实现可以*地重新排序这两个fwrite,并将它们合并到一个写入中。


1: See the strike-through text in R..'s answer for an example.

1:在R.. .看文章。这是一个例子。