March 25, 2007
James Dennett wrote:
> Walter Bright wrote:
> That would be true, except that Andrei wrote that
> the guarantee applied to separate processes, and
> that can only be guaranteed if you both use some
> kind of synchronization between the processes *and*
> flush the stream.
> 
> Andrei's claim went beyond mere thread-safety, and
> that was what I responded to.

Ok, but since it is typical to do a flush on newline if isatty(), that seems to resolve these inter-process problems.

> There's a place:
> 
> locked(cout) << a << b;
> 
> can be made do the job, using RAII to lock at the
> start of the expression and unlock at the end.

I don't think it is that easy, see: http://docs.sun.com/source/819-3690/Multithread.html
and http://www.atnf.csiro.au/computing/software/sol2docs/manuals/c++/lib_ref/MT.html


> Right.  I certainly did not intend to imply that any
> serious design would be silly enough to lock for each
> character written (which would be fairly useless
> synchronization in any case).

It's needed if only to avoid corrupting the I/O buffer itself.

>> The problem is such synchronization would be invented and added on by
>> the user, making it impossible to combine disparate libraries that write
>> to stderr, for example, in a multithreading environment.
> Most libraries ought not to do so; coding dependencies
> on globals into libraries is generally poor design.

I think it is unreasonable to tell users they cannot use standard cin/cout/cerr in standard ways in their library code.

> The problem is not that users would have to write
> synchronization.  Usually they need to do that. A
> problem would be if some low-level locking inside
> the I/O subsystems gave the impression that the
> user did *not* need to synchronize their own code.
> 
> It's not quite as simple as this.  One (possibly
> killer) argument for building synchronization into
> low-level libraries is to reduce the cost of
> dealing with support issues from bemused users
> who expected not to have to consider thread-safety
> when sharing streams between threads.

I think it is a killer argument. Multithreaded programming is hard enough without heaping more burdens on the user.
March 25, 2007
James Dennett wrote:
> As you appear to be saying that printf has to flush every
> time it's used, I'd guess that it's unusable for performance
> reasons alone.

Numbers clearly tell the above is wrong. Here's the thing: I write programs that write lines to files. If I use cout, they don't work. If I use fprintf, the do work, and 10 times faster. And that's that.

> It's also really hard to implement such a
> guarantee on most platforms without using some kind of
> process-shared mutex, file lock, or similar.  Does printf
> really incur that kind of overhead every time something is
> written to a stream, or does its implementation make use
> of platform-specific knowledge on which writes are atomic
> at the OS level?

The C standard library takes care of it without me having to do anything in particular.

> Within a process, this level of safety could be achieved
> with only a little (usually redundant) synchronization.
> Which is useful for debugging or simplistic logging,but
> not for anything else I've seen.

I do not concur.


Andrei
March 25, 2007
Andrei Alexandrescu (See Website For Email) wrote:
> James Dennett wrote:
>> As you appear to be saying that printf has to flush every time it's used, I'd guess that it's unusable for performance reasons alone.
> 
> Numbers clearly tell the above is wrong.

Only if they apply to the above.

> Here's the thing: I write
> programs that write lines to files. If I use cout, they don't work. If I
> use fprintf, the do work, and 10 times faster. And that's that.

Except that your test wasn't of the right thing; you
probably didn't test code that guaranteed atomicity
of writes between different processes.

>> It's also really hard to implement such a
>> guarantee on most platforms without using some kind of
>> process-shared mutex, file lock, or similar.  Does printf
>> really incur that kind of overhead every time something is
>> written to a stream, or does its implementation make use
>> of platform-specific knowledge on which writes are atomic
>> at the OS level?
> 
> The C standard library takes care of it without me having to do anything in particular.

I've never seen a C library that guarantees atomicity of
writes between processes on a Unix-like system.  The
documentation of some systems does guarantee atomicity
of sufficiently small writes to certain types of file
descriptors, but I've not seen any Unix-like system
that guarantees atomicity for writes of unlimited sizes;
in some cases they can even be interrupted before the
full amount is written.  I've certainly seen the result
of C's *printf *not* being synchronized between processes
on a wide variety of systems.

>> Within a process, this level of safety could be achieved
>> with only a little (usually redundant) synchronization.
>> Which is useful for debugging or simplistic logging,but
>> not for anything else I've seen.
> 
> I do not concur.

With my description of my own experience?  ;)

-- James
March 25, 2007
Walter Bright wrote:
> Sean Kelly wrote:
>> There really isn't any way to do automatic locking with chained opCall barring the use of proxy objects or something equally nasty. Also, it hurts efficiency to always lock regardless of whether the user is performing IO in multiple threads.  The preferred method here is:
>>
>> synchronized( Cout )
>>     Cout( a )( b )( c )();
> 
> The trouble with that design is people working on subsystems or libraries, which will be combined by others into a working whole. Since it is extra work to add the synchronized statement, odds are pretty good it won't happen. Then, the whole gets erratic multithreading performance.
> 
> Ideally, things should be inverted so that thread safety is the default behavior, and the extra-efficiency-dammit-I-know-what-I'm-doing is the extra work.
> 
> One way to solve this problem is to use variadic templates as outlined in http://www.digitalmars.com/d/variadic-function-templates.html
> 
> Back in the early days of Windows NT, when multithreaded programming was introduced to a mass platform, C compilers typically shipped with two runtime libraries - a single threaded one "for efficiency", and a multithreaded one. Also, to do multithreaded code, one had to predefine _MT or throw a command line switch. Inevitably, this was overlooked, and endless bugs consumed endless time. I made the decision early on to only ship threadsafe libraries, and have _MT always on. I've never regretted it, I'm sure it saved me a lot of tech support time, and avoided the perception that the compiler didn't work with multithreading.

MS does the same now if I remember correctly: all of its libraries are MT by default.

I agree with Walter's sentiment that Cout(a)(b) is a design mistake. Fortunately, now we have compile-time variadic functions, which will make it easy to correct the design - Cout(a, b) can be made just as good without having to chase typeinfo's at runtime.


Andrei
March 25, 2007
James Dennett wrote:
> Walter Bright wrote:
>> James Dennett wrote:
>>> Andrei Alexandrescu (See Website For Email) wrote:
>>>> cout << a << b;
>>>>
>>>> can't guarantee that a and b will be adjacent in the output. In
>>>> contrast,
>>>>
>>>> printf(format, a, b);
>>>>
>>>> does give that guarantee. Moreover, that guarantee is not between
>>>> separate threads in the same process, it's between whole processes!
>>>> Guess which of the two is usable :o).
>>> As you appear to be saying that printf has to flush every
>>> time it's used, I'd guess that it's unusable for performance
>>> reasons alone.
>> In order for printf to work right it does not need to flush every time
>> (you're right in that would lead to terrible performance). The usual
>> thing that printf does is only do a flush if isatty() comes back with
>> true. In fact, flushing the output at the end of each printf would not
>> mitigate multithreading problems at all. In order for printf to be
>> thread safe, all that's necessary is for it to acquire/release the C
>> stream lock (C's implementation of stdio has a lock associated with each
>> stream).
> 
> That would be true, except that Andrei wrote that
> the guarantee applied to separate processes, and
> that can only be guaranteed if you both use some
> kind of synchronization between the processes *and*
> flush the stream.
> 
> Andrei's claim went beyond mere thread-safety, and
> that was what I responded to.

Lines don't have to appear at exact times, they only must not interleave. So printf does not have to flush often. I've used printf-level atomicity for a long time on various systems and it works perfectly.

Is a system-dependent assumption? I don't know. It sure is there and is very helpful on all systems I used it with.


Andrei
March 25, 2007
Andrei Alexandrescu (See Website For Email) wrote:
> James Dennett wrote:
>> Walter Bright wrote:
>>> James Dennett wrote:
>>>> Andrei Alexandrescu (See Website For Email) wrote:
>>>>> cout << a << b;
>>>>>
>>>>> can't guarantee that a and b will be adjacent in the output. In contrast,
>>>>>
>>>>> printf(format, a, b);
>>>>>
>>>>> does give that guarantee. Moreover, that guarantee is not between separate threads in the same process, it's between whole processes! Guess which of the two is usable :o).
>>>> As you appear to be saying that printf has to flush every time it's used, I'd guess that it's unusable for performance reasons alone.
>>> In order for printf to work right it does not need to flush every time (you're right in that would lead to terrible performance). The usual thing that printf does is only do a flush if isatty() comes back with true. In fact, flushing the output at the end of each printf would not mitigate multithreading problems at all. In order for printf to be thread safe, all that's necessary is for it to acquire/release the C stream lock (C's implementation of stdio has a lock associated with each stream).
>>
>> That would be true, except that Andrei wrote that
>> the guarantee applied to separate processes, and
>> that can only be guaranteed if you both use some
>> kind of synchronization between the processes *and*
>> flush the stream.
>>
>> Andrei's claim went beyond mere thread-safety, and
>> that was what I responded to.
> 
> Lines don't have to appear at exact times, they only must not interleave. So printf does not have to flush often. I've used printf-level atomicity for a long time on various systems and it works perfectly.

With sufficiently short lines, where the value of
"sufficiently" depends on which platform and which
kind of file descriptor you're writing to.  printf
is likely to end up calling write with no locking;
write isn't atomic past a certain (or uncertain)
size, and has no reason to make the boundary
coincide with the end of a line.

> Is a system-dependent assumption? I don't know. It sure is there and is very helpful on all systems I used it with.

Can you name one specific system where this is
documented as working reliably, or where it can
be shown to do so?  I've *seen* interleaving
between processes, and lived with it in debugging
code for performance reasons, but for reliable
output have used other mechanisms.  I understood
this to be a widely known problem with printf,
write et al.

-- James
March 25, 2007
Andrei Alexandrescu (See Website For Email) wrote:
> Walter Bright wrote:
>>
>> Back in the early days of Windows NT, when multithreaded programming was introduced to a mass platform, C compilers typically shipped with two runtime libraries - a single threaded one "for efficiency", and a multithreaded one. Also, to do multithreaded code, one had to predefine _MT or throw a command line switch. Inevitably, this was overlooked, and endless bugs consumed endless time. I made the decision early on to only ship threadsafe libraries, and have _MT always on. I've never regretted it, I'm sure it saved me a lot of tech support time, and avoided the perception that the compiler didn't work with multithreading.
> 
> MS does the same now if I remember correctly: all of its libraries are MT by default.

Yup.  In fact, I just discovered that Visual Studio 2005 doesn't even provide a single-threaded build option any more.  In some ways it's a relief because it's allowed me to drop two build options and remove a bunch of #if defined(_MT) clauses.

> I agree with Walter's sentiment that Cout(a)(b) is a design mistake. Fortunately, now we have compile-time variadic functions, which will make it easy to correct the design - Cout(a, b) can be made just as good without having to chase typeinfo's at runtime.

Agreed.


Sean
March 25, 2007
James Dennett wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> James Dennett wrote:
>>> As you appear to be saying that printf has to flush every
>>> time it's used, I'd guess that it's unusable for performance
>>> reasons alone.
>> Numbers clearly tell the above is wrong. 
> 
> Only if they apply to the above.
> 
>> Here's the thing: I write
>> programs that write lines to files. If I use cout, they don't work. If I
>> use fprintf, the do work, and 10 times faster. And that's that.
> 
> Except that your test wasn't of the right thing; you
> probably didn't test code that guaranteed atomicity
> of writes between different processes.
> 
>>> It's also really hard to implement such a
>>> guarantee on most platforms without using some kind of
>>> process-shared mutex, file lock, or similar.  Does printf
>>> really incur that kind of overhead every time something is
>>> written to a stream, or does its implementation make use
>>> of platform-specific knowledge on which writes are atomic
>>> at the OS level?
>> The C standard library takes care of it without me having to do anything
>> in particular.
> 
> I've never seen a C library that guarantees atomicity of
> writes between processes on a Unix-like system.  The
> documentation of some systems does guarantee atomicity
> of sufficiently small writes to certain types of file
> descriptors, but I've not seen any Unix-like system
> that guarantees atomicity for writes of unlimited sizes;
> in some cases they can even be interrupted before the
> full amount is written.  I've certainly seen the result
> of C's *printf *not* being synchronized between processes
> on a wide variety of systems.

If you did, fine. I take that part of my argument back. I'll also note that that doesn't make iostreams any more defensible :o).

Andrei
March 25, 2007
Andrei Alexandrescu (See Website For Email) wrote:

[snip]

> I'll also note
> that that doesn't make iostreams any more defensible :o).

Trying to defend IOStreams is certainly a challenge.

I think I've tried enough, given what a sick puppy it
is, and now should leave it to suffer in peace.

-- James

March 25, 2007
Walter Bright wrote:
> 
> D's implementation of writef does the same thing. D's writef also wraps the whole thing in a try-finally, making it exception safe.
> 
> Iostreams'
>     cout << a << b;
> results in the equivalent of:
>     (cout->out(a))->out(b);
> The trouble is, there's no place to hang the lock acquire/release, nor the try-finally. It's a fundamental design problem.

The stream could acquire a lock and pass it to a proxy object which closes the lock on destruction.  This would work fine in C++ where the lifetime of such objects is deterministic, but the design is incredibly awkward.

>> It's also really hard to implement such a
>> guarantee on most platforms without using some kind of
>> process-shared mutex, file lock, or similar.  Does printf
>> really incur that kind of overhead every time something is
>> written to a stream,
> 
> It does exactly one lock acquire/release for each printf, not for each character written.

This is still far too granular for most uses.  About the only time I actually use output without explicit synchronization are for throw-away debug output.

>> or does its implementation make use
>> of platform-specific knowledge on which writes are atomic
>> at the OS level?
>>
>> Within a process, this level of safety could be achieved
>> with only a little (usually redundant) synchronization.
> 
> The problem is such synchronization would be invented and added on by the user, making it impossible to combine disparate libraries that write to stderr, for example, in a multithreading environment.

This is a valid point, but how often is it actually used in practice? Libraries generally do not perform error output of their own, and applications typically have a coherent approach for output.  In my time as a programmer, I can't think of a single instance where default synchronization to an output device actually mattered.  I can certainly appreciate this for its predictable behavior, but I don't know how often that predictability would actually matter to me.

>> Which is useful for debugging or simplistic logging,but
>> not for anything else I've seen.

Exactly.


Sean