简体   繁体   English

运算符<<(ostream&,obj)在两个不同的流线程上是否安全?

[英]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 . 在g ++ 6.2.1中编译上面的代码,然后用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. 似乎两个线程都调用了locale_facet.h:widen ,导致数据竞争,因为在这个函数中没有同步,即使在两个不同的ostringstream对象上调用了operator << So I was wondering whether this is really a data race or just a false positive of helgrind . 所以我想知道这是真的是数据竞赛还是只是helgrind

Update : I admit I didn't fully read the question before answering, so I took it upon myself to research this. 更新 :我承认在回答之前我没有完全阅读这个问题,所以我自己去研究这个问题。

The TL;DR 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. ios每个语言环境都有静态变量,这些静态变量延迟加载(首次需要时初始化)查找表。 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 初始化流时,它会填充一个指针,该指针将加载到语言环境的正确ctype中(请参阅_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 该错误指的是: 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();

对于2个不同的流,它的线程安全:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM