45

Can anything bad happen (like undefined behavior, file corruption, etc.) if several threads concurrently call fflush() on the same FILE* variable?

Clarification: I don't mean writing the file concurrently. I only mean flushing it concurrently.

The threads do not read or write the file concurrently (they only write the file inside a critical section, one thread at a time). They only flush outside of the critical section, to release the critical section sooner so to let the others do the other work (except file writing).

Though it may happen that one thread is writing the file (inside the critical section), while another thread(s) is flushing the file (outside the critical section).

Serge Rogatch
  • 13,865
  • 7
  • 86
  • 158
  • This sounds as though you are asking it from a design perspective, rather than trying to eliminate an explanation for a bug — perhaps it would be worth making that more explicit. – PJTraill Aug 24 '16 at 10:54
  • 1
    Given the answer that `FILE`s are thread-safe, one negative consequence could be that one thread is forced to wait for another to flush, though that would probably only matter if you require fairly high performance. Using functions like `flockfile` ¿or `flock`? could _conceivably_ lead to deadlock, but probably not in the case you describe. – PJTraill Aug 24 '16 at 11:07
  • @PJTraill , that's all right: if one thread is flushing, and another thread writes something and then figures out it need to flush too, then all it can do is to wait for the first thread to return what's at `FILE*` into a consistent state (flushing everything it has written, and maybe even what the second thread has written - this depends on the race), and then the second thread should ensure that what it has just written gets flushed too. Of course there is a better approach with asynchronous flushing in a dedicated thread, but that's more complex thus out of the scope. – Serge Rogatch Aug 27 '16 at 16:48
  • You seem to assume that flushing only delays other threads also trying to flush, but my understanding of the quotes in 2501’s answer is that, since there is a _single_ lock per stream, it also delays threads trying to write — and that could be undesirable! – PJTraill Aug 31 '16 at 14:11

5 Answers5

40

Streams in C1 are thread-safe2. Functions are required to lock the stream before accessing it3.

The fflush function is thread-safe and may be called from any thread at any time, as long as the stream is an output stream or an update stream4.


1 As per the current standard, which is C11.

2 (Quoted from: ISO/IEC 9899:201x 7.21.3 Streams 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.

3 (Quoted from: ISO/IEC 9899:201x 7.21.3 Streams 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. reentrant: a single thread may hold the lock multiple times at a given time.

4 (Quoted from: ISO/IEC 9899:201x 7.21.5.2 The fflush function 2)
If stream points to an output stream or an update stream in which the most recent operation was not input, the fflush function causes any unwritten data for that stream to be delivered to the host environment to be written to the file; otherwise, the behavior is undefined.

2501
  • 25,460
  • 4
  • 47
  • 87
  • 1
    Does MSVC compiler follow these on x86/x86_64 Windows? – Serge Rogatch Aug 20 '16 at 14:24
  • @SergeRogatch If it is C11 compliant, then yes. – cat Aug 20 '16 at 14:29
  • @SergeRogatch Aparently it is safe: https://msdn.microsoft.com/en-us/library/9yky46tz.aspx – 2501 Aug 20 '16 at 14:35
  • 8
    @cat VS is not even C99 compliant. – 2501 Aug 20 '16 at 14:35
  • 1
    @2501 Eheheh, well, what hope is there for the universe? Maybe they should just encourage their users to use Clang instead. – cat Aug 20 '16 at 14:42
  • 1
    @2501, yes, great, for both 2013 and 2015 they say "This function locks the calling thread and is therefore thread-safe. For a non-locking version, see _fflush_nolock." on that MSDN page. – Serge Rogatch Aug 20 '16 at 14:42
  • _"The lock is"_ What? – edmz Aug 20 '16 at 15:26
  • 1
    @black a faulty copy-paste. Thanks. – 2501 Aug 20 '16 at 15:37
  • 2
    C streams were not always safe. I recall having to track down a bug where they were not. Nevertheless, if you hit such a bug today, bug your compiler vendor. – Joshua Aug 20 '16 at 15:53
  • 3
    @cat Microsoft Visual C++ aims to be a C++ compiler, not a C compiler. AFAIK they do support C++14 in recent versions. – user253751 Aug 21 '16 at 04:25
  • I welcome useful corrections, but please stop editing my answer to change its look. – 2501 Aug 21 '16 at 10:39
  • 1
    We use `fwrite` and `fflush` with the same `FILE` concurrently in code compiled with MSVC 2015 without any problems. The writes are being neatly interleaved in the output file. – rustyx Aug 23 '16 at 18:29
12

The POSIX.1 and C-language functions that operate on character streams (represented by pointers to objects of type FILE) are required by POSIX.1c to be implemented in such a way that reentrancy is achieved (see ISO/IEC 9945:1-1996, §8.2).

This requirement has a drawback; it imposes substantial performance penalties because of the synchronization that must be built into the implementations of the functions for the sake of reentrancy. POSIX.1c addresses this tradeoff between reentrancy (safety) and performance by introducing high-performance, but non-reentrant (potentially unsafe), versions of the following C-language standard I/O functions: getc(), getchar(), putc() and putchar(). The non-reentrant versions are named getc_unlocked(), and so on, to stress their unsafeness.

Note however as others have noted: Many popular systems (including Windows and Android) are not POSIX compliant.

geocar
  • 9,085
  • 1
  • 29
  • 37
  • ▲ For “_not POSIX compliant_”, but do you mean that `fflush` is not thread safe, that they do not implement POSIX.1c or that they do not claim compliance? A list (or link to one) of platforms (with version information) and their compliance _in this respect_ might be a useful resource. – PJTraill Aug 24 '16 at 11:00
  • I don't know if Google promises that `fflush` will be thread-safe, [even if it currently is](https://sourceforge.net/u/lluct/me722-cm/ci/86c46fc79a15bc9500fbb47241a15c148b8abb01/tree/bionic/libc/stdio/fflush.c), but Microsoft [does](https://msdn.microsoft.com/en-us/library/9yky46tz.aspx) at least on Windows. – geocar Aug 24 '16 at 11:03
9

You are not supposed to call fflush() on an input stream, it invokes undefined behavior, so I shall assume the stream is open in write or update mode.

If the stream is open in update mode ("w+" or "r+"), the last operation must not be a read when you call fflush(). Since the stream is used in various threads asynchronously, it would be difficult to ensure this without some form of interprocess communication and synchronization or locking if you do any reads. There is still a valid reason to open the file in update mode, but make sure you do not do any reads after you start the fflush thread.

fflush() does not modify the current position. It merely causes any buffered output to be written to the system. The streams are usually protected by a lock, so calling fflush() in one thread should not mess the output performed by another thread, but it may change the timing of the system writes. If multiple threads output the the same FILE*, the order in which the interleaving occurs is indeterminate anyway. Furthermore, if you use fseek() is different threads for the same stream, you must use a lock of your own to ensure consistency between the fseek() and the following output.

Although it seems OK to do it, it is probably not recommended. You could instead call fflush() after the write operations in each thread, before releasing the lock.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
1

Pretty simple answer, you may not do that, as the file has a single "current position". How do you keep track of it? Is the file opened for sequential access or random? In the latter case you could open it multiple times (one for each thread), and yet devise ways to keep the file structure consistent.

Constantine Georgiou
  • 2,412
  • 1
  • 13
  • 17
  • Ok, I should add this to the question: the threads do not read or write the file concurrently (they only write the file inside a critical section, one thread at a time). They only flush outside of the critical section, to release the critical section sooner so to let the others do the other work (except file writing). – Serge Rogatch Aug 20 '16 at 12:32
  • 3
    `fflush()` does not modify the current position. – chqrlie Aug 20 '16 at 12:38
  • It could be easier then, I would consider a different way, create another worker thread (no windows, timers, message-queues etc) which will only be performing the flushing (or maybe the writing as well?). The thread proc would only contain a loop with a SleepEx() command. The other threads would be sending APC requests for writng, and the APC proc would execute them (one at a time, as they are queued). Implementation is only a few source lines, and you don't really need to read much, if you don't know how to do it already. See the documentation for QueueUserAPC() function. – Constantine Georgiou Aug 20 '16 at 13:45
0

The actual answer seems to be that streams are (meant to be) thread-safe themselves, but, if that weren't the case, your issue could be the fflush occurs (outside a lock) while another thread is writing (inside a critical section).

As such, I'd work with the model of the virtual machine you're coding against and put the fflush() inside a critical section too.

Mark Hurd
  • 10,665
  • 10
  • 68
  • 101