2017-01-31 29 views
14
#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(); 
} 

用g ++ 6.2.1编译上面的代码,然后用valgrind --tool=helgrind ./a.out运行它。 Helgrind会抱怨:运算符<<(ostream&,obj)在两个不同的线程上是否安全?

==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" 

看来,这两个线程调用locale_facet.h:widen造成数据竞争,因为有出现在该函数没有同步,即使operator <<被称为在两个不同的ostringstream对象。所以我想知道这是真的是数据竞赛还是只是一个误报helgrind

+3

不管是什么标准说,这应该* *是线程安全的。 –

+2

标准状态*多线程并发访问流对象(27.8,27.9),流缓冲对象(27.6)或C库流(27.9.2) 可能会导致数据竞争(1.10),除非另有说明( 27.4)。 [注意:数据竞赛 导致未定义的行为(1.10)。 - 结束注释] *所以我的猜测是流在后端使用了一些不同步的全局状态。或者这是一个误报。我的直觉说这应该是安全的。 – NathanOliver

+2

我认为这应该是安全的根据§17.6.5.9/ 2:*“C++标准库函数不应直接或间接访问除当前线程之外的线程可访问的对象(1.10),除非通过直接或间接访问对象函数的参数,包括'this'。“*所以我会说这是一个不符合的实现或者是一个误报。 –

回答

0

对于2个不同的数据流是线程安全的:)

+2

这将是很好的供应支持答案 –

1

更新:我承认我没有完全回答之前阅读的问题,所以我把它在我研究这个。

的TL; DR

这里有可变成员变量可能导致竞争条件。 ios每个语言环境都有静态变量,并且这些静态变量延迟加载(首次需要时初始化)查找表。如果要避免并发性问题,只需确保在加入线程之前初始化区域设置,以便任何线程操作都只能读取到这些查找表中。

的详细

当流被初始化它填充它加载在正确CTYPE为该区域的指针(见_M_ctype): https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/basic_ios.h#L273

的误差指: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/locale_facets.h#L875

如果两个线程同时初始化相同的语言环境,则这可能是竞态条件。

这应该是线程安全的(虽然它可能仍然给错误):

// 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(); 
+0

你可以给出一些参考? – lz96

+0

在一个更大的程序中,在分割线程之前做了很多事情,这可能不成问题。 – ymmyk

+0

这是一个标准缺陷还是libstdC++中的一个错误? – lz96

相关问题