Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is operator<<(ostream&, obj) on two different streams thread safe?

#include <iostream>
#include <sstream>
#include <thread>

using namespace std;

int main()
{
    auto runner = []() {
        ostringstream oss;
        for (int i=0; i<100000; ++i)
            oss << i;
    };

    thread t1(runner), t2(runner);
    t1.join(); t2.join();
}

Compile the above code in g++6.2.1, then run it with valgrind --tool=helgrind ./a.out. Helgrind would complain:

==5541== ----------------------------------------------------------------
==5541== 
==5541== Possible data race during read of size 1 at 0x51C30B9 by thread #3
==5541== Locks held: none
==5541==    at 0x4F500CB: widen (locale_facets.h:875)
==5541==    by 0x4F500CB: widen (basic_ios.h:450)
==5541==    by 0x4F500CB: fill (basic_ios.h:374)
==5541==    by 0x4F500CB: std::ostream& std::ostream::_M_insert<long>(long) (ostream.tcc:73)
==5541==    by 0x400CD0: main::{lambda()#1}::operator()() const (43.cpp:12)
==5541==    by 0x4011F7: void std::_Bind_simple<main::{lambda()#1} ()>::_M_invoke<>(std::_Index_tuple<>) (functional:1391)
==5541==    by 0x401194: std::_Bind_simple<main::{lambda()#1} ()>::operator()() (functional:1380)
==5541==    by 0x401173: std::thread::_State_impl<std::_Bind_simple<main::{lambda()#1} ()> >::_M_run() (thread:197)
==5541==    by 0x4EF858E: execute_native_thread_routine (thread.cc:83)
==5541==    by 0x4C31A04: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so)
==5541==    by 0x56E7453: start_thread (in /usr/lib/libpthread-2.24.so)
==5541==    by 0x59E57DE: clone (in /usr/lib/libc-2.24.so)
==5541== 
==5541== This conflicts with a previous write of size 8 by thread #2
==5541== Locks held: none
==5541==    at 0x4EF3B1F: do_widen (locale_facets.h:1107)
==5541==    by 0x4EF3B1F: std::ctype<char>::_M_widen_init() const (ctype.cc:94)
==5541==    by 0x4F501B7: widen (locale_facets.h:876)
==5541==    by 0x4F501B7: widen (basic_ios.h:450)
==5541==    by 0x4F501B7: fill (basic_ios.h:374)
==5541==    by 0x4F501B7: std::ostream& std::ostream::_M_insert<long>(long) (ostream.tcc:73)
==5541==    by 0x400CD0: main::{lambda()#1}::operator()() const (43.cpp:12)
==5541==    by 0x4011F7: void std::_Bind_simple<main::{lambda()#1} ()>::_M_invoke<>(std::_Index_tuple<>) (functional:1391)
==5541==    by 0x401194: std::_Bind_simple<main::{lambda()#1} ()>::operator()() (functional:1380)
==5541==    by 0x401173: std::thread::_State_impl<std::_Bind_simple<main::{lambda()#1} ()> >::_M_run() (thread:197)
==5541==    by 0x4EF858E: execute_native_thread_routine (thread.cc:83)
==5541==    by 0x4C31A04: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so)
==5541==  Address 0x51c30b9 is 89 bytes inside data symbol "_ZN12_GLOBAL__N_17ctype_cE"

It seems that both threads called locale_facet.h:widen which caused data race since there appears no synchronization in this function, even though operator << is called on two different ostringstream object. So I was wondering whether this is really a data race or just a false positive of helgrind.

like image 391
lz96 Avatar asked Jan 31 '17 17:01

lz96


People also ask

What is ostream operator?

A function that takes and returns a stream object. It generally is a manipulator function. The standard manipulators which have an effect when used on standard ostream objects are: manipulator.

What is an ostream object?

ostream class − The ostream class handles the output stream in c++ programming language. These output stream objects are used to write data as a sequence of characters on the screen. cout and puts handle the out streams in c++ programming language.

What type is ostream?

ostream is a general purpose output stream. cout and cerr are both examples of ostreams. ifstream is an input file stream. It is a special kind of an istream that reads in data from a data file.

Which operator is Overloadable?

Overloadable operators The true and false operators must be overloaded together. Must be overloaded in pairs as follows: == and != , < and > , <= and >= .


1 Answers

Update: I admit I didn't fully read the question before answering, so I took it upon myself to research this.

The TL;DR

There are mutable member variables here that could cause race condition. ios has static variables per locale, and these static variables lazy load (initialized when first needed) lookup tables. If you want to avoid concurrency problems, just be sure to initialize the locale prior to joining the threads so that any thread operation is read only to these lookup tables.

The Details

When a stream is initialized it populates a pointer which loads in the correct ctype for the locale (see _M_ctype): https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/basic_ios.h#L273

The error is referring to: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/locale_facets.h#L875

This could be a race condition if two thread are simultaneously initializing the same locale.

This should be thread-safe (though it may still give error):

// Force ctype to be initialized in the base thread before forking
ostringstream dump;
dump << 1;

auto runner = []() {
    ostringstream oss;
    for (int i=0; i<100000; ++i)
        oss << i;
};

thread t1(runner), t2(runner);
t1.join(); t2.join();
like image 140
ymmyk Avatar answered Oct 16 '22 01:10

ymmyk