libstdc++/10193: Regression: iostreams no longer threadsafe

Loren James Rittle rittle@latour.rsch.comm.mot.com
Mon Apr 28 22:00:00 GMT 2003


(I have moved this response to the libstdc++ list so that other
maintainers may comment.)

In article <07D05A69A3D0C14FAEA60C3ACE8E5564028F556B@nike.hir.is>,
Pétur Runólfsson<peturr02@ru.is> writes:

> I don't agree with this: The most basic reason
> containers can't be made thread-safe is that they return
> references to internal data structures, but I/O operations
> return by value.

Here is the party line as I understand it (and agree with it):

According to our published rules in libstdc++-v3 (taken from SGI):
Whether something should internally lock or not is predicated alone on
visibility into the internal object structure of the library, as
dictated by the standard, w.r.t. the external programmer.  We assume
that the libc ABI we layer upon is thread-safe for this same
definition (if it is not, we break; we would probably accept generic
changes to better support all cases where a particular libc ABI entry
point is not typically thread-safe).

If an IO object a and b internally share a resource, and the sharing
is not directly knowable to the programmer, the library must
internally lock.  If the IO object c is shared by the programmer
between threads or is itself a global object, then the programmer must
handle the locking protocol.

> I also disagree with this because thread-safe
> iostreams implementations exist (most notably gcc-2.95); using
> such an implementation without external locking may be somewhat
> tricky, but is in many cases possible.

BTW, I agree that there is proof by existence in that libstdc++-v2
worked with libio in a manner you describe.  But let me play the
devil's advocate here.  At what granularity would you like your IO
operations interleaved?  How is the intention of the programmer
revealed to the library implementation?  Who, other than the
programmer, shall declare how the following output is to be
interleaved?

void foo(void) {
  std::cout << "hel";
  //signal A, barrier (waiting for B)
  std::cout << "wor";
  //signal C
}

main() {
  start_thread_foo (&foo);
  //barrier (waiting for A)
  std::cout << "lo ";
  //signal B, barrier (waiting for C)
  std::cout << "ld!" << std::endl;
}

Only the user knew where to put the locking code.  Of course, we might
state that std::cout, etc must lock internally as well to allow user
code which refuses to add it to at least "work" (i.e. not crash).
However, that would be overhead.  How many lock/unlock cycles are in
(or even possible with C++)?

    std::cout << "ld!" << std::endl;

Two is the answer implied by the number of method calls on std::cout.

Regards,
Loren



More information about the Libstdc++ mailing list