2017-01-31 17 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 Library (27.9.2) несколькими потоками может привести к гонке данных (1.10), если не указано иное (27,4). [Примечание: Гонки данных приводят к неопределенному поведению (1.10). -end note] * Итак, я предполагаю, что потоки используют некоторое глобальное состояние в задней части, которое не синхронизировано. Или это ложный позитив. Моя кишка говорит, что это должно быть безопасно. – NathanOliver

+2

Я думаю, что это должно быть безопасно в соответствии с §17.6.5.9/2: * «Стандартная библиотечная функция C++ не должна прямо или косвенно обращаться к объектам (1.10), доступным для потоков, отличных от текущего потока, если только объекты не доступны прямо или косвенно через аргументы функции, в том числе 'this'. * * Поэтому я бы сказал, что это либо несоответствующая реализация, либо ложный позитив. –

ответ

0

Для 2-х различных потоков это поточно :)

+2

Было бы полезно указать ссылки, поддерживающие ответ –

1

Update: Я признаю, что я полностью не читал этот вопрос, прежде чем ответить, так что я взял на себя, чтобы исследовать это.

Модель 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