ISO/IEC JTC1 SC22 WG21 N3678 - 2013-04-30
Lawrence Crowl, crowl@google.com, Lawrence@Crowl.org
Introduction
Solution
Stream Guard
Guard Stream
Expression Locking
Block Locking
Recursive Locking
Wording
27.7.6 Stream Guard [stream.guard]
27.7.6.1 Class template stream_guard
[stream.guard.class]
27.7.6.1.1 Types [stream.guard.class.types]
27.7.6.1.2 Constructors and destructors [stream.guard.class.ctor]
27.7.6.1.3 Member functions [stream.guard.class.memfn]
27.7.6.2 Function template guard_stream
[stream.guard.fnctn]
27.7.6.3 Streaming function templates [stream.guard.streaming]
Revision History
References
At present, stream output operations guarantee that they will not produce race conditions, but do not guarantee that the effect will be sensible. Some form of external synchronization is required. Unfortunately, without a standard mechanism for synchronizing, independently developed software will be unable to synchronize.
N3535 C++ Stream Mutexes proposed a standard mechanism for finding and sharing a mutex on streams. At the Spring 2013 standards meeting, the Concurrency Study Group requested a change away from a full mutex definition to a definition that also enabled buffering. This paper describes such a change.
A draft of this paper was reviewed in the Library Working Group,
and found too many open issues on what was reasonably exposed
to the 'buffering' part.
In retrospect, I think that if buffering is desired it should be explicit.
So, this paper is neither as general as a stream mutex
nor as sure as an explicit string_stream
buffer.
Nevertheless, this paper represents a design point
that needs documentation and discussion.
First, we propose a stream_guard
,
that serves to batch operations on a stream.
That batching may be implemented as mutexes, as buffering,
or some combination of both.
A stream guard is declared with a reference to the stream to be locked. Each stream to be locked may have more than one simultaneous associated stream guard object. Two independent threads creating two stream guard objects on the same stream will mutually exclude blocks of operations on the stream.
std::ostringstream stream;
stream_guard<std::ostream> gout(std::cout);
To avoid requiring programmers to specify the stream_guard type, we provide a template function that returns the properly typed object from a reference to the stream.
auto gout = guard_stream(std::cout);
Programmers batch operations in a single full expression.
guard_stream(std::cout) << "1" << "2" << "3" << "4" << "5" << std::endl;
Locking across more than one expression is needed, and the stream guard serves that purpose as well.
{
auto gout = guard_stream(std::cout);
gout << "1";
gout << "2";
gout << "3";
gout << "4";
gout << "5";
gout << std::endl;
}
Streams may be locked in uncoordinated code,
and so recursive locking must work in some fashion.
Because the implementation may use buffering or mutexes,
the order of output in the recursive case is unspecified
except that when a stream_guard
destruction
happens before another guarded operation on that stream,
the operations of the first guard happen before the operations on the second.
{
auto gout = guard_stream(std::cout);
gout << "1";
guard_stream(std::cout) << "2";
gout << "3" << std::endl;
}
The output could be 123 or 213 but not 132 (because of the destructor happens-before order) and not 231 or (because of the output operations sequence) and not 312 or 321 (because of both).
This wording is relative to N3485.
Add a new section.
The header
<stream_guard>
defines class and function templates that synchronize access to streams.
stream_guard
[stream.guard.class]Add a new section.
There are two stream class templates, one for input and one for output.
template <typename Stream> class stream_guard; template <typename charT, typename traits> class stream_guard<std::basic_istream<charT, traits>> template <typename charT, typename traits> class stream_guard<std::basic_ostream<charT, traits>> template <typename charT, typename traits> class stream_guard<std::basic_wistream<charT, traits>> template <typename charT, typename traits> class stream_guard<std::basic_wostream<charT, traits>>
Add a new section.
Both class templates provide the following typedefs to the same named types in the constructor parameter types.
fmtflags, locale, iostate, char_type, traits_type, event_callback, off_type, pos_type
The input class template provides the following typedef to the same named type in the constructor parameter type.
int_type
Add a new section.
stream_guard(std::basic_istream<charT, traits>& stm);
stream_guard(std::basic_ostream<charT, traits>& stm);
stream_guard(std::basic_wistream<charT, traits>& stm);
stream_guard(std::basic_wostream<charT, traits>& stm);
Effects: Constructs a
stream_guard
and initiates a sequence of batched stream operations.Synchronization: May or may not acquire a mutex for arbitrary portions of the lifetime of the object.
~stream_guard();
Effects: Finalizes a sequence of batched operations, ensuring that all operations are effective on the stream.
If construction and destruction of a single guard occur on different threads, the effects are undefined.
For two guards on a given stream:
If construction and destruction are on the same thread:
If the destruction of the first guard is sequenced before (1.9) the construction of the second guard, the effects on the stream of the first guard are sequenced before the effects on the stream of the second guard.
Otherwise, if the destruction of the first guard is sequenced before (1.9) the destruction of the second guard, the effects on the stream of the first guard are sequenced before the effects on the second stream that are sequenced after the first guard's destruction.
Otherwise, the effects may or may not be interleaved, but shall preserve the sequence on individual guards.
Otherwise, the two guards are constructed and destructed on separate threads:
If the destruction of the first guard happens before (1.10) the construction of the second guard, the effects on the stream of the first guard happen before the construction of the second guard.
Otherwise, the effects of one guard happen before the effects of the other guard.
Destroys the holder.
Synchronization: May or may not acquire a mutex for arbitrary portions of the lifetime of the object.
Add a new section.
Both class templates provides the following functions, which have the same effect as the same functions on the constructor argument object.
fmtflags flags() const; fmtflags flags(fmtflags fmtfl); fmtflags setf(fmtflags fmtfl); fmtflags setf(fmtflags fmtfl, fmtflags mask); void unsetf(fmtflags mask); std::streamsize precision() const; std::streamsize precision(std::streamsize prec); std::streamsize width() const; std::streamsize width(std::streamsize wide); locale getloc() const; long& iword(int index); void*& pword(int index); void register_callback(event_callback fn, int index); explicit operator bool() const; iostate rdstate() const; void clear(iostate state = goodbit); void setstate(iostate state); bool good() const; bool eof() const; bool fail() const; bool bad() const; iostate exceptions() const; void exceptions(iostate except); std::basic_ostream<char_type,traits_type>* tie() const; std::basic_ostream<char_type,traits_type>* tie(std::basic_ostream<char_type,traits_type>* tiestr); std::basic_streambuf<char_type,traits_type>* rdbuf() const; std::basic_streambuf<char_type,traits_type>* rdbuf(std::basic_streambuf<char_type,traits_type>* sb); std::basic_ios<char_type,traits_type>& copyfmt(const std::basic_ios<char_type,traits_type>& rhs); char_type fill() const; char_type fill(char_type ch); locale imbue(const locale& loc); char narrow(char_type c, char dfault) const; char_type widen(char c) const;
The input class template provides the following functions, which have the same effect as the same functions on the constructor argument object.
std::streamsize gcount() const; int_type get(); std::basic_istream<charT,traits>& get(charT& c); std::basic_istream<charT,traits>& get(charT* s, std::streamsize n); std::basic_istream<charT,traits>& get(charT* s, std::streamsize n, charT delim); std::basic_istream<charT,traits>& get(std::basic_streambuf<charT,traits>& sb); std::basic_istream<charT,traits>& get(std::basic_streambuf<charT,traits>& sb, charT delim); std::basic_istream<charT,traits>& getline(charT* s, std::streamsize n); std::basic_istream<charT,traits>& getline(charT* s, std::streamsize n, charT delim); std::basic_istream<charT,traits>& ignore(std::streamsize n = 1, int_type delim = traits::eof()); int_type peek(); std::basic_istream<charT,traits>& read(charT* s, std::streamsize n); std::streamsize readsome(charT* s, std::streamsize n); std::basic_istream<charT,traits>& putback(charT c); std::basic_istream<charT,traits>& unget(); int sync(); pos_type tellg(); std::basic_istream<charT,traits>& seekg(pos_type p); std::basic_istream<charT,traits>& seekg(off_type o, std::ios_base::seekdir s);The output class template provides the following functions, which have the same effect as the same functions on the constructor argument object.
std::basic_ostream<charT,traits>& put(charT c); std::basic_ostream<charT,traits>& write(const charT* s, std::streamsize n); std::basic_ostream<charT,traits>& flush(); pos_type tellp(); std::basic_ostream<charT,traits>& seekp(pos_type p); std::basic_ostream<charT,traits>& seekp(off_type o, std::ios_base::seekdir s);
guard_stream
[stream.guard.fnctn]Add a new section.
There are two stream function templates, one for input and one for output, that construct
stream_guard
objects.
template <typename charT, typename traits>
stream_guard<std::basic_istream<charT, traits>>
guard_stream(std::istream& arg);
template <typename charT, typename traits>
stream_guard<std::basic_ostream<charT, traits>>
guard_stream(std::ostream& arg);
template <typename charT, typename traits>
stream_guard<std::basic_wistream<charT, traits>>
guard_stream(std::istream& arg);
template <typename charT, typename traits>
stream_guard<std::basic_wostream<charT, traits>>
guard_stream(std::ostream& arg);Effects: Constructs and returns a
stream_guard
object.
Add a new section.
There are two function templates, that forward streaming operations to the stream.
template <typename charT, typename traits, typename T>
const stream_guard<std::basic_istream<charT, traits>>&
operator>>(const stream_guard<std::basic_istream<charT, traits>>& grd,
const T&& arg);
template <typename charT, typename traits, typename T>
const stream_guard<std::basic_ostream<charT, traits>>&
operator<<(const stream_guard<std::basic_ostream<charT, traits>>& grd,
const T& arg);
template <typename charT, typename traits>
const stream_guard<std::basic_istream<charT, traits>>&
operator>>(const stream_guard<std::basic_istream<charT, traits>>& grd,
std::basic_istream<charT, traits>&
(*arg)(std::basic_istream<charT, traits>&));
template <typename charT, typename traits>
const stream_guard<std::basic_ostream<charT, traits>>&
operator<<(const stream_guard<std::basic_ostream<charT, traits>>& grd,
std::basic_ostream<charT, traits>&
(*arg)(std::basic_ostream<charT, traits>&));Effects: Apply the effects of the corresponding operations on the
stream_guard
constructor argument object.
This paper presents and alternate approach to N3535 - 2013-04-16.
Remove support for the mutex concept in order to permit implementation as a buffer that is transferred to the stream at the end. Remove corresponding document sections.
Make full-expression locking explicit. Remove the intermediate object and do all work on construction on the guard.
Create typedefs and forwarding functions to the stream equivalents. (This change is necessary because there is no longer any access to the underlying stream, which may be a different buffer stream.)
Remove 'Critique and Suggestions' section.
Replace 'Synopsis' section with 'Wording' section.
Remove 'Implementation' section.
N3535 revised N3395 - 2012-09-23 as follows.
Choose the approach of maintaining a stream to mutex mapping, which allows multiple stream_mutex objects to manage a single stream.
Add an elaboration of the consequences of the above decision to the 'Critique and Suggestions' section.
Add a discussion of replacing streams to the 'Critique and Suggestions' section.
Add a 'Revision History' section.
N3395 revised N3354 = 12-0044 - 2012-01-14 as follows.
Clarify usefulness of stream mutexes in debugging, testing, and diagnostics.
Apply common subexpression elimination
on calls to bypass()
in the examples.
Add a 'Critique and Suggestions' section discussing the topics of Posix stream locks, internal stream locks, and maintaining a stream to mutex mapping.
Add an 'Implementation' section.
Add a section on 'Predefined Objects' corresponding to the standard streams.
Add a 'References' section.