2017-01-31 61 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

相關問題