memcpy [или нет?] и многопоточность [std::thread из C++11]
Я пишу программное обеспечение на C/C++, используя много BIAS/Profil, библиотеку интервальной алгебры. В моем алгоритме у меня есть мастер, который делит домен и передает его части в подчиненный процесс (ы). Они возвращают статут об этих частях домена. Есть общие данные для чтения и все.
Мне нужно распараллелить мой код, однако, как только 2 подчиненных потока запущены (или, я полагаю, больше), и обе они вызывают функции этой библиотеки, происходит сбой. Что характерно для этих segfaults, так это то, что gdb редко указывает на одну и ту же строку ошибки из двух сборок: это зависит от скорости потоков, если она была запущена ранее, и т. Д. Я пытался заставить потоки выдавать результаты до тех пор, пока мастер, он "стабилизирует" ошибку. Я вполне уверен, что это происходит из-за обращений к memcpy из библиотеки (после обратной трассировки gdb я всегда заканчиваю тем, что функция BIAS / Profil вызывает memcpy. Честно говоря, почти все функции вызывают memcpy для временного объект до возврата результата...). Из того, что я читал в Интернете, может показаться, что memcpy() может быть не поточно-ориентированным, в зависимости от реализаций (особенно здесь). (Это кажется странным для функции, предназначенной только для чтения общих данных... или, может быть, при записи потоковых данных оба потока используют одну и ту же область памяти?)
Чтобы попытаться решить эту проблему, я бы хотел "заменить" (по крайней мере, для тестов, если поведение изменилось) вызов memcpy для вызова с мьютексной рамкой. (что-то вроде mtx.lock (); mempcy (...); mtx.unlock ();)
1-й вопрос: я вообще не являюсь инженером / разработчиком кода, и мне не хватает базовых знаний. Я думаю, что, поскольку я использую предварительно собранную библиотеку BIAS/Profil, вызванный memcpy является той системой, на которой была построена библиотека, правильно? Если это так, изменилось бы что-нибудь, если бы я попытался собрать библиотеку из исходного кода в моей системе? (Я не уверен, что смогу построить эту библиотеку, поэтому вопрос.)
2-й вопрос: в моем string.h memcpy объявлен:#ifndef __HAVE_ARCH_MEMCPY
extern void * memcpy(void *,const void *,__kernel_size_t);
#endif
и в некоторых других строковых заголовках (string_64.h, string_32.h) определение формы: #define memcpy(dst, src, len) __inline_memcpy((dst), (src), (len))
или какое-то более явное определение, или просто объявление, подобное приведенному. Это начинает уродливо, но в идеале я хотел бы создать переменную препроцессора #define __HAVE_ARCH_MEMCPY 1
и void * memcpy(void *,const void *,__kernel_size_t)
который сделал бы memcpy в рамке мьютекса с отклоненным memcpy. Идея здесь состоит в том, чтобы избежать путаницы с библиотекой и заставить ее работать с 3 строками кода;)
Есть идея получше? (это сделало бы мой день...)
3 ответа
Учитывая, что ваши наблюдения, и что Profil lib из прошлого тысячелетия, и что документация (домашняя страница и Profil2.ps
) даже не содержат слова "поток", я бы предположил, что библиотека не является потокобезопасным.
1-й: нет, обычно memcpy
является частью libc, которая динамически связана (по крайней мере, в настоящее время). На Linux, проверьте с ldd NAMEOFBINARY
, который должен дать строку с чем-то вроде libc.so.6 => /lib/i386-linux-gnu/libc.so.6
или похожие. Если нет: восстановите. Если да: восстановление может помочь в любом случае, так как есть много других факторов.
Помимо этого, я думаю, memcpy
Потокобезопасен до тех пор, пока вы никогда не будете записывать данные (даже запись неизмененных данных повредит: https://blogs.oracle.com/dave/entry/memcpy_concurrency_curiosities).
2-й: если окажется, что вы должны использовать модифицированный memcpy
также подумайте о LD_PRELOAD
,
ИМХО, вам следует сосредоточиться не на функциях memcpy (), а на функциональности более высокого уровня.
И memcpy () является поточно-ориентированным, если обрабатываемые интервалы памяти параллельно работающих потоков не перекрываются. Практически, в memcpy () есть только цикл for(;;) (с большим количеством оптимизаций) [по крайней мере, в glibc], это причина, почему она объявлена.
Если вы хотите знать, что будут делать ваши параллельные потоки memcpy (), вы должны представить циклы for(;;), которые копируют память через указатели longint.
В общем случае вы должны использовать критическую секцию, мьютекс или какую-либо другую технику защиты, чтобы не допустить одновременного доступа нескольких потоков к функциям, не поддерживающим потоки. Некоторые реализации ANSI C memcpy()
не являются потокобезопасными, некоторые ( безопасно, не безопасно)
Написание функций, которые являются поточно-ориентированными, и / или написание многопоточных программ, которые могут безопасно поддерживать не поточно-безопасные функции, является важной темой. Очень выполнимо, но требует чтения по теме. Там много написано. Это, по крайней мере, поможет вам начать задавать правильные вопросы.