GNU Info

Info Node: (libc.info)Streams and Threads

(libc.info)Streams and Threads


Next: Streams and I18N Prev: Closing Streams Up: I/O on Streams
Enter node , (file) or (file)node

Streams and Threads
===================

   Streams can be used in multi-threaded applications in the same way
they are used in single-threaded applications.  But the programmer must
be aware of a the possible complications.  It is important to know about
these also if the program one writes never use threads since the design
and implementation of many stream functions is heavily influenced by the
requirements added by multi-threaded programming.

   The POSIX standard requires that by default the stream operations are
atomic.  I.e., issuing two stream operations for the same stream in two
threads at the same time will cause the operations to be executed as if
they were issued sequentially.  The buffer operations performed while
reading or writing are protected from other uses of the same stream.  To
do this each stream has an internal lock object which has to be
(implicitly) acquired before any work can be done.

   But there are situations where this is not enough and there are also
situations where this is not wanted.  The implicit locking is not enough
if the program requires more than one stream function call to happen
atomically.  One example would be if an output line a program wants to
generate is created by several function calls.  The functions by
themselves would ensure only atomicity of their own operation, but not
atomicity over all the function calls.  For this it is necessary to
perform the stream locking in the application code.

 - Function: void flockfile (FILE *STREAM)
     The `flockfile' function acquires the internal locking object
     associated with the stream STREAM.  This ensures that no other
     thread can explicitly through `flockfile'/`ftrylockfile' or
     implicit through a call of a stream function lock the stream.  The
     thread will block until the lock is acquired.  An explicit call to
     `funlockfile' has to be used to release the lock.

 - Function: int ftrylockfile (FILE *STREAM)
     The `ftrylockfile' function tries to acquire the internal locking
     object associated with the stream STREAM just like `flockfile'.
     But unlike `flockfile' this function does not block if the lock is
     not available.  `ftrylockfile' returns zero if the lock was
     successfully acquired.  Otherwise the stream is locked by another
     thread.

 - Function: void funlockfile (FILE *STREAM)
     The `funlockfile' function releases the internal locking object of
     the stream STREAM. The stream must have been locked before by a
     call to `flockfile' or a successful call of `ftrylockfile'.  The
     implicit locking performed by the stream operations do not count.
     The `funlockfile' function does not return an error status and the
     behavior of a call for a stream which is not locked by the current
     thread is undefined.

   The following example shows how the functions above can be used to
generate an output line atomically even in multi-threaded applications
(yes, the same job could be done with one `fprintf' call but it is
sometimes not possible):

     FILE *fp;
     {
        ...
        flockfile (fp);
        fputs ("This is test number ", fp);
        fprintf (fp, "%d\n", test);
        funlockfile (fp)
     }

   Without the explicit locking it would be possible for another thread
to use the stream FP after the `fputs' call return and before `fprintf'
was called with the result that the number does not follow the word
`number'.

   From this description it might already be clear that the locking
objects in streams are no simple mutexes.  Since locking the same
stream twice in the same thread is allowed the locking objects must be
equivalent to recursive mutexes.  These mutexes keep track of the owner
and the number of times the lock is acquired.  The same number of
`funlockfile' calls by the same threads is necessary to unlock the
stream completely.  For instance:

     void
     foo (FILE *fp)
     {
       ftrylockfile (fp);
       fputs ("in foo\n", fp);
       /* This is very wrong!!!  */
       funlockfile (fp);
     }

   It is important here that the `funlockfile' function is only called
if the `ftrylockfile' function succeeded in locking the stream.  It is
therefore always wrong to ignore the result of `ftrylockfile'.  And it
makes no sense since otherwise one would use `flockfile'.  The result
of code like that above is that either `funlockfile' tries to free a
stream that hasn't been locked by the current thread or it frees the
stream prematurely.  The code should look like this:

     void
     foo (FILE *fp)
     {
       if (ftrylockfile (fp) == 0)
         {
           fputs ("in foo\n", fp);
           funlockfile (fp);
         }
     }

   Now that we covered why it is necessary to have these locking it is
necessary to talk about situations when locking is unwanted and what can
be done.  The locking operations (explicit or implicit) don't come for
free.  Even if a lock is not taken the cost is not zero.  The operations
which have to be performed require memory operations that are safe in
multi-processor environments.  With the many local caches involved in
such systems this is quite costly.  So it is best to avoid the locking
completely if it is not needed - because the code in question is never
used in a context where two or more threads may use a stream at a time.
This can be determined most of the time for application code; for
library code which can be used in many contexts one should default to be
conservative and use locking.

   There are two basic mechanisms to avoid locking.  The first is to use
the `_unlocked' variants of the stream operations.  The POSIX standard
defines quite a few of those and the GNU library adds a few more.
These variants of the functions behave just like the functions with the
name without the suffix except that they do not lock the stream.  Using
these functions is very desirable since they are potentially much
faster.  This is not only because the locking operation itself is
avoided.  More importantly, functions like `putc' and `getc' are very
simple and traditionally (before the introduction of threads) were
implemented as macros which are very fast if the buffer is not empty.
With the addition of locking requirements these functions are no longer
implemented as macros since they would would expand to too much code.
But these macros are still available with the same functionality under
the new names `putc_unlocked' and `getc_unlocked'.  This possibly huge
difference of speed also suggests the use of the `_unlocked' functions
even if locking is required.  The difference is that the locking then
has to be performed in the program:

     void
     foo (FILE *fp, char *buf)
     {
       flockfile (fp);
       while (*buf != '/')
         putc_unlocked (*buf++, fp);
       funlockfile (fp);
     }

   If in this example the `putc' function would be used and the
explicit locking would be missing the `putc' function would have to
acquire the lock in every call, potentially many times depending on when
the loop terminates.  Writing it the way illustrated above allows the
`putc_unlocked' macro to be used which means no locking and direct
manipulation of the buffer of the stream.

   A second way to avoid locking is by using a non-standard function
which was introduced in Solaris and is available in the GNU C library
as well.

 - Function: int __fsetlocking (FILE *STREAM, int TYPE)
     The `__fsetlocking' function can be used to select whether the
     stream operations will implicitly acquire the locking object of the
     stream STREAM.  By default this is done but it can be disabled and
     reinstated using this function.  There are three values defined
     for the TYPE parameter.

    `FSETLOCKING_INTERNAL'
          The stream `stream' will from now on use the default internal
          locking.  Every stream operation with exception of the
          `_unlocked' variants will implicitly lock the stream.

    `FSETLOCKING_BYCALLER'
          After the `__fsetlocking' function returns the user is
          responsible for locking the stream.  None of the stream
          operations will implicitly do this anymore until the state is
          set back to `FSETLOCKING_INTERNAL'.

    `FSETLOCKING_QUERY'
          `__fsetlocking' only queries the current locking state of the
          stream.  The return value will be `FSETLOCKING_INTERNAL' or
          `FSETLOCKING_BYCALLER' depending on the state.

     The return value of `__fsetlocking' is either
     `FSETLOCKING_INTERNAL' or `FSETLOCKING_BYCALLER' depending on the
     state of the stream before the call.

     This function and the values for the TYPE parameter are declared
     in `stdio_ext.h'.

   This function is especially useful when program code has to be used
which is written without knowledge about the `_unlocked' functions (or
if the programmer was too lazy to use them).


automatically generated by info2www version 1.2.2.9