C++ Library Reference |
Using Classic
iostreams
in a Multithreading EnvironmentThis chapter describes how to use the
iostream
classes of thelibC
andlibiostream
libraries for input-output (I/O) in a multithreaded environment. It also provides examples of how to extend functionality of the library by deriving from theiostream
classes. This chapter is not a guide for writing multithreaded code in C++, however.The discussion here applies only to the old iostreams (
libC
andlibiostream
) and does not apply tolibCstd
, the new iostream that is part of the C++ Standard Library.4.1 Multithreading
Multithreading (MT) is a powerful facility that can speed up applications on multiprocessors; it can also simplify the structuring of applications on both multiprocessors and uniprocessors. The
iostream
library has been modified to allow its interfaces to be used by applications in a multithreaded environment by programs that utilize the multithreading capabilities when running Solaris version 2.6, 7, or 8 of the Solaris operating environment. Applications that utilize the single-threaded capabilities of previous versions of the library are not affected by the behavior of the modifiediostream
interfaces.A library is defined to be MT-safe if it works correctly in an environment with threads. Generally, this "correctness" means that all of its public functions are reentrant. The
iostream
library provides protection against multiple threads that attempt to modify the state of objects (that is, instances of a C++ class) shared by more than one thread. However, the scope of MT-safety for aniostream
object is confined to the period in which the object's public member function is executing.
Caution An application is not automatically guaranteed to be MT-safe because it uses MT-safe objects from thelibC
library. An application is defined to be MT-safe only when it executes as expected in a multithreaded environment.
4.2 Organization of the MT-Safe
iostream
LibraryThe organization of the MT-safe
iostream
library is slightly different from other versions of theiostream
library. The exported interface of the library refers to thepublic
andprotected
member functions of theiostream
classes and the set of base classes available, and is consistent with other versions; however, the class hierarchy is different. See Section 4.3 "Interface Changes to the iostream Library" for details.The original core classes have been renamed with the prefix
unsafe_
. TABLE 4-1 lists the classes that are the core of theiostream
package.
Each MT-safe class is derived from the base class
stream_MT
. Each MT-safe class, exceptstreambuf
, is also derived from the existingunsafe_
base class. Here are some examples:
class streambuf: public stream_MT { ... };class ios: virtual public unsafe_ios, public stream_MT { ... };class istream: virtual public ios, public unsafe_istream { ... };The class
stream_MT
provides the mutual exclusion (mutex) locks required to make eachiostream
class MT-safe; it also provides a facility that dynamically enables and disables the locks so that the MT-safe property can be dynamically changed. The basic functionality for I/O conversion and buffer management are organized into theunsafe_
classes; the MT-safe additions to the library are confined to the derived classes. The MT-safe version of each class contains the same protected and public member functions as theunsafe_ base
class. Each member function in the MT-safe version class acts as a wrapper that locks the object, calls the same function in theunsafe_ base
class, and unlocks the object.
Note The classstreambuf
is not derived from an unsafe class. The public and protected member functions of classstreambuf
are reentrant by locking. Unlocked versions, suffixed with_unlocked
, are also provided.
4.2.1 Public Conversion Routines
A set of reentrant public functions that are MT-safe have been added to the
iostream
interface. A user-specified buffer is an additional argument to each function. These functions are described as follows.
Caution The public conversion routines of theiostream
library (oct
,hex
,dec
,chr
, andform
) that are present to ensure compatibility with an earlier version oflibC
are not MT-safe.
4.2.2 Compiling and Linking with the MT-Safe
libC
LibraryWhen you build an application that uses the
iostream
classes of thelibC
library to run in a multithreaded environment, compile and link the source code of the application using the-mt
option. This option passes-D_REENTRANT
to the preprocessor and-lthread
to the linker.
Note Use-mt
(rather than-lthread
) to link withlibC
andlibthread
. This option ensures proper linking order of the libraries. Using-lthread
improperly could cause your application to work incorrectly.
Single-threaded applications that use
iostream
classes do not require special compiler or linker options. By default, the compiler links with thelibC
library.4.2.3 MT-Safe
iostream
RestrictionsThe restricted definition of MT-safety for the
iostream
library means that a number of programming idioms used withiostream
are unsafe in a multithreaded environment using sharediostream
objects.4.2.3.1 Checking Error State
To be MT-safe, error checking must occur in a critical region with the I/O operation that causes the error. The following example illustrates how to check for errors:
In this example, the constructor of the
stream_locker
objectsl
locks theistream
objectistr
. The destructor ofsl
, called at the termination ofread_number
, unlocksistr
.4.2.3.2 Obtaining Characters Extracted by Last Unformatted Input Operation
To be MT-safe, the
gcount
function must be called within a thread that has exclusive use of theistream
object for the period that includes the execution of the last input operation andgcount
call. The following example shows a call togcount
:
CODE EXAMPLE 4-2 Calling gcount
void fetch_line(istream& istr, char* line, int& linecount) { stream_locker sl(istr, stream_locker::lock_defer); sl.lock(); // lock the stream istr istr >> line; linecount = istr.gcount(); sl.unlock(); // unlock istr ... }In this example, the
lock
andunlock
member functions of classstream_locker
define a mutual exclusion region in the program.4.2.3.3 User-Defined I/O Operations
To be MT-safe, I/O operations defined for a user-defined type that involve a specific ordering of separate operations must be locked to define a critical region. The following example shows a user-defined I/O operation:
CODE EXAMPLE 4-3 User-Defined I/O Operations
class mystream: public istream { // other definitions... int getRecord(char* name, int& id, float& gpa); }; int mystream::getRecord(char* name, int& id, float& gpa) { stream_locker sl(this, stream_locker::lock_now); *this >> name; *this >> id; *this >> gpa; return this->fail() == 0; }4.2.4 Performance
Using the MT-safe classes in this version of the
libC
library results in some amount of performance overhead, even in a single-threaded application; however, if you use theunsafe_
classes oflibC
, this overhead can be avoided.The scope resolution operator can be used to execute member functions of the base
unsafe_
classes; for example:
cout.unsafe_ostream::put('4');
cin.unsafe_istream::read(buf, len);
Note Theunsafe_
classes cannot be safely used in multithreaded applications.
Instead of using
unsafe_
classes, you can make thecout
andcin
objectsunsafe
and then use the normal operations. A slight performance deterioration results. The following example shows how to useunsafe
cout
andcin
:
CODE EXAMPLE 4-4 Disabling MT-Safety
//disable mt-safetycout.set_safe_flag(stream_MT::unsafe_object); //disable mt-safetycin.set_safe_flag(stream_MT::unsafe_object); cout.put(`4'); cin.read(buf, len);When an
iostream
object is MT-safe, mutex locking is provided to protect the object's member variables. This locking adds unnecessary overhead to an application that only executes in a single-threaded environment. To improve performance, you can dynamically switch aniostream
object to and from MT-safety. The following example makes aniostream
object MT-unsafe:
CODE EXAMPLE 4-5 Switching to MT-Unsafe
fs.set_safe_flag(stream_MT::unsafe_object);// disable MT-safety .... do various i/o operationsYou can safely use an MT-unsafe stream in code where an iostream is not shared by threads; for example, in a program that has only one thread, or in a program where each iostream is private to a thread.
If you explicitly insert synchronization into the program, you can also safely use MT-unsafe
iostreams
in an environment where aniostream
is shared by threads. The following example illustrates the technique:
CODE EXAMPLE 4-6 Using Synchronization with MT-Unsafe Objects
generic_lock() ; fs.set_safe_flag(stream_MT::unsafe_object) ; ... do various i/o operations generic_unlock() ;where the
generic_lock
andgeneric_unlock
functions can be any synchronization mechanism that uses such primitives as mutex, semaphores, or reader/writer locks.
Note Thestream_locker
class provided by thelibC
library is the preferred mechanism for this purpose.
See Section 4.6 "Object Locks" for more information.
4.3 Interface Changes to the
iostream
LibraryThis section describes the interface changes made to the
iostream
library to make it MT-Safe.4.3.1 New Classes
The following table lists the new classes added to the
libC
interfaces.
CODE EXAMPLE 4-7 New Classes
stream_MT stream_locker unsafe_ios unsafe_istream unsafe_ostream unsafe_iostream unsafe_fstreambase unsafe_strstreambase4.3.2 New Class Hierarchy
The following table lists the new class hierarchy added to the
iostream
interfaces.
CODE EXAMPLE 4-8 New Class Hierarchy
class streambuf : public stream_MT { ... }; class unsafe_ios { ... }; class ios : virtual public unsafe_ios, public stream_MT { ... }; class unsafe_fstreambase : virtual public unsafe_ios { ... }; class fstreambase : virtual public ios, public unsafe_fstreambase { ... }; class unsafe_strstreambase : virtual public unsafe_ios { ... }; class strstreambase : virtual public ios, public unsafe_strstreambase { ... }; class unsafe_istream : virtual public unsafe_ios { ... }; class unsafe_ostream : virtual public unsafe_ios { ... }; class istream : virtual public ios, public unsafe_istream { ... }; class ostream : virtual public ios, public unsafe_ostream { ... }; class unsafe_iostream : public unsafe_istream, public unsafe_ostream { ... };4.3.3 New Functions
The following table lists the new functions added to the
iostream
interfaces.
CODE EXAMPLE 4-9 New Functions
class streambuf { public: int sgetc_unlocked(); void sgetn_unlocked(char *, int); int snextc_unlocked(); int sbumpc_unlocked(); void stossc_unlocked(); int in_avail_unlocked(); int sputbackc_unlocked(char); int sputc_unlocked(int); int sputn_unlocked(const char *, int); int out_waiting_unlocked(); protected: char* base_unlocked(); char* ebuf_unlocked(); int blen_unlocked(); char* pbase_unlocked(); char* eback_unlocked(); char* gptr_unlocked(); char* egptr_unlocked(); char* pptr_unlocked(); void setp_unlocked(char*, char*); void setg_unlocked(char*, char*, char*); void pbump_unlocked(int); void gbump_unlocked(int); void setb_unlocked(char*, char*, int); int unbuffered_unlocked(); char *epptr_unlocked(); void unbuffered_unlocked(int); int allocate_unlocked(int); }; class filebuf : public streambuf { public: int is_open_unlocked(); filebuf* close_unlocked(); filebuf* open_unlocked(const char*, int, int = filebuf::openprot); filebuf* attach_unlocked(int); }; class strstreambuf : public streambuf { public: int freeze_unlocked(); char* str_unlocked(); }; unsafe_ostream& endl(unsafe_ostream&); unsafe_ostream& ends(unsafe_ostream&); unsafe_ostream& flush(unsafe_ostream&); unsafe_istream& ws(unsafe_istream&); unsafe_ios& dec(unsafe_ios&); unsafe_ios& hex(unsafe_ios&); unsafe_ios& oct(unsafe_ios&); char* dec_r (char* buf, int buflen, long num, int width) char* hex_r (char* buf, int buflen, long num, int width) char* oct_r (char* buf, int buflen, long num, int width) char* chr_r (char* buf, int buflen, long chr, int width) char* str_r (char* buf, int buflen, const char* format, int width = 0); char* form_r (char* buf, int buflen, const char* format, ...)4.4 Global and Static Data
Global and static data in a multithreaded application are not safely shared among threads. Although threads execute independently, they share access to global and static objects within the process. If one thread modifies such a shared object, all the other threads within the process observe the change, making it difficult to maintain state over time. In C++, class objects (instances of a class) maintain state by the values in their member variables. If a class object is shared, it is vulnerable to changes made by other threads.
When a multithreaded application uses the
iostream
library and includesiostream.h
, the standard streams--cout
,cin
,cerr
, andclog
-- are, by default, defined as global shared objects. Since theiostream
library is MT-safe, it protects the state of its shared objects from access or change by another thread while a member function of aniostream
object is executing. However, the scope of MT-safety for an object is confined to the period in which the object's public member function is executing. For example,
int c;cin.get(c);gets the next character in the
get
buffer and updates the buffer pointer in ThreadA. However, if the next instruction in ThreadA is anotherget
call, thelibC
library does not guarantee to return the next character in the sequence. It is not guaranteed because, for example, ThreadB may have also executed theget
call in the intervening period between the twoget
calls made in ThreadA.See Section 4.6 "Object Locks" for strategies for dealing with the problems of shared objects and multithreading.
4.5 Sequence Execution
Frequently, when
iostream
objects are used, a sequence of I/O operations must be MT-safe. For example, the code:
cout << " Error message:" << errstring[err_number] << "\n";involves the execution of three member functions of the
cout
stream object. Sincecout
is a shared object, the sequence must be executed atomically as a critical section to work correctly in a multithreaded environment. To perform a sequence of operations on aniostream
class object atomically, you must use some form of locking.The
libC
library now provides thestream_locker
class for locking operations on aniostream
object. See Section 4.6 "Object Locks" for information about thestream_locker
class.4.6 Object Locks
The simplest strategy for dealing with the problems of shared objects and multithreading is to avoid the issue by ensuring that
iostream
objects are local to a thread. For example,
- Declare objects locally within a thread's entry function.
- Declare objects in thread-specific data. (For information on how to use thread specific data, see the
thr_keycreate
(3T) man page.)- Dedicate a stream object to a particular thread. The object thread is
private
by convention.However, in many cases, such as default shared standard stream objects, it is not possible to make the objects local to a thread, and an alternative strategy is required.
To perform a sequence of operations on an
iostream
class object atomically, you must use some form of locking. Locking adds some overhead even to a single-threaded application. The decision whether to add locking or makeiostream
objects private to a thread depends on the thread model chosen for the application: Are the threads to be independent or cooperating?
- If each independent thread is to produce or consume data using its own
iostream
object, theiostream
objects are private to their respective threads and locking is not required.- If the threads are to cooperate (that is, they are to share the same
iostream
object), then access to the shared object must be synchronized and some form of locking must be used to make sequential operations atomic.4.6.1 Class
stream_locker
The
iostream
library provides thestream_locker
class for locking a series of operations on aniostream
object. You can, therefore, minimize the performance overhead incurred by dynamically enabling or disabling locking iniostream
objects.Objects of class s
tream_locker
can be used to make a sequence of operations on a stream object atomic. For example, the code shown in the example below seeks to find a position in a file and reads the next block of data.
CODE EXAMPLE 4-10 Example of Using Locking Operations
{ const int len = 128; char buf[len]; int offset = 48; stream_locker s_lock(fs, stream_locker::lock_now); . . . . .// open file fs.seekg(offset, ios::beg); fs.read(buf, len); }In this example, the constructor for the
stream_locker
object defines the beginning of a mutual exclusion region in which only one thread can execute at a time. The destructor, called after the return from the function, defines the end of the mutual exclusion region. Thestream_locker
object ensures that both the seek to a particular offset in a file and the read from the file are performed together, atomically, and that ThreadB cannot change the file offset before the original ThreadA reads the file.An alternative way to use a
stream_locker
object is to explicitly define the mutual exclusion region. In the following example, to make the I/O operation and subsequent error checking atomic,lock
andunlock
member function calls of a vbstream_locker
object are used.
CODE EXAMPLE 4-11 Making I/O Operation and Error Checking Atomic
{ ... stream_locker file_lck(openfile_stream,stream_locker::lock_defer); .... file_lck.lock(); // lock openfile_stream openfile_stream << "Value: " << int_value << "\n"; if(!openfile_stream) { file_error("Output of value failed\n"); return; } file_lck.unlock(); // unlock openfile_stream }For more information, see the
stream_locker
(3CC4) man page.4.7 MT-Safe Classes
You can extend or specialize the functionality of the
iostream
classes by deriving new classes. If objects instantiated from the derived classes will be used in a multithreaded environment, the classes must be MT-safe.Considerations when deriving MT-safe classes include:
- Making a class object MT-safe by protecting the internal state of the object from multiple-thread modification. To do this, serialize access to member variables in public and protected member functions with mutex locks.
- Making a sequence of calls to member functions of an MT-safe base class atomic, using a
stream_locker
object.- Avoiding locking overhead by using the
_unlocked
member functions ofstreambuf
within critical regions defined bystream_locker
objects.- Locking the public virtual functions of class
streambuf
in case the functions are called directly by an application. These functions are:xsgetn
,underflow
,pbackfail
,xsputn
,overflow
,seekoff
, andseekpos
.- Extending the formatting state of an
ios
object by using the member functionsiword
andpword
in classios
. However, a problem can occur if more than one thread is sharing the same index to aniword
orpword
function. To make the threads MT-safe, use an appropriate locking scheme.- Locking member functions that return the value of a member variable greater in size than a
char
.4.8 Object Destruction
Before an
iostream
object that is shared by several threads is deleted, the main thread must verify that the subthreads are finished with the shared object. The following example shows how to safely destroy a shared object.
CODE EXAMPLE 4-12 Destroying a Shared Object
#include <fstream.h> #include <thread.h> fstream* fp; void *process_rtn(void*) { // body of sub-threads which uses fp... } multi_process(const char* filename, int numthreads) { fp = new fstream(filename, ios::in); // create fstream object // before creating threads. // create threads for (int i=0; i<numthreads; i++) thr_create(0, STACKSIZE, process_rtn, 0, 0, 0); ... // wait for threads to finish for (int i=0; i<numthreads; i++) thr_join(0, 0, 0); delete fp; // delete fstream object after fp = NULL; // all threads have completed. }4.9 An Example Application
The following code provides an example of a multiply-threaded application that uses
iostream
objects from thelibC
library in an MT-safe way.The example application creates up to 255 threads. Each thread reads a different input file, one line at a time, and outputs the line to an output file, using the standard output stream,
cout
. The output file, which is shared by all threads, is tagged with a value that indicates which thread performed the output operation.
CODE EXAMPLE 4-13 Using iostream
Objects in an MT-Safe Way
// create tagged thread data // the output file is of the form: // <tag><string of data>\n // where tag is an integer value in a unsigned char. // Allows up to 255 threads to be run in this application // <string of data> is any printable characters // Because tag is an integer value written as char, // you need to use od to look at the output file, suggest: // od -c out.file |more #include <stdlib.h> #include <stdio.h> #include <iostream.h> #include <fstream.h> #include <thread.h> struct thread_args { char* filename; int thread_tag; }; const int thread_bufsize = 256; // entry routine for each thread void* ThreadDuties(void* v) { // obtain arguments for this thread thread_args* tt = (thread_args*)v; char ibuf[thread_bufsize]; // open thread input file ifstream instr(tt->filename); stream_locker lockout(cout, stream_locker::lock_defer); while(1) { // read a line at a time instr.getline(ibuf, thread_bufsize - 1, '\n'); if(instr.eof()) break; // lock cout stream so the i/o operation is atomic lockout.lock(); // tag line and send to cout cout << (unsigned char)tt->thread_tag << ibuf << "\n"; lockout.unlock(); } return 0; } int main(int argc, char** argv) { // argv: 1+ list of filenames per thread if(argc < 2) { cout << "usage: " << argv[0] << " <files..>\n"; exit(1); } int num_threads = argc - 1; int total_tags = 0; // array of thread_ids thread_t created_threads[thread_bufsize]; // array of arguments to thread entry routine thread_args thr_args[thread_bufsize]; int i; for( i = 0; i < num_threads; i++) { thr_args[i].filename = argv[1 + i]; // assign a tag to a thread - a value less than 256 thr_args[i].thread_tag = total_tags++; // create threads thr_create(0, 0, ThreadDuties, &thr_args[i], THR_SUSPENDED, &created_threads[i]); } for(i = 0; i < num_threads; i++) { thr_continue(created_threads[i]); } for(i = 0; i < num_threads; i++) { thr_join(created_threads[i], 0, 0); } return 0; }
Sun Microsystems, Inc. Copyright information. All rights reserved. Feedback |
Library | Contents | Previous | Next | Index |