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 make iostream 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, the iostream 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.
The iostream library provides the stream_locker class for locking a series of operations on an iostream object. You can, therefore, minimize the performance overhead incurred by dynamically enabling or disabling locking in iostream objects.
Objects of class stream_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.
#include <fstream.h> #include <rlocks.h> void lock_example (fstream& fs) { 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. The stream_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 and unlock member function calls of a vbstream_locker object are used.
{ ... 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 } |