зависание и/или segfault при использовании boost::threads из Matlab, а не при прямом вызове

В чем была проблема, на случай, если у людей возникнет похожая проблема: после некоторых обсуждений с поддержкой Mathworks выяснилось, что это конфликт между системным повышением и библиотеками, поставляемыми Matlab: когда я скомпилировал с системным повышением заголовки и связаны с (более старыми) библиотеками повышения Matlab, это привело к ошибке. Когда я скомпилировал и динамически связал с системным повышением, но затем он динамически загрузил библиотеки повышения Matlab, он завис навсегда.

Статическая привязка к системе boost работает, как и загрузка правильных заголовков для версии boost, с которой поставляется Matlab, и компиляция с ними. Конечно, сборки Matlab для Mac не имеют номеров версий в своих именах файлов, хотя в сборках Linux и, предположительно, Windows они есть. Для справки R2011b использует Boost 1.44.


У меня есть многопоточный код, который отлично работает, когда он скомпилирован напрямую, но segfaults и/или тупиковые ситуации, когда он вызывается из интерфейса Matlab mex. Я не знаю, выявляет ли другая среда недостаток в моем коде или что, но я не могу понять это....

Я запускаю это на трех конфигурациях машин (хотя есть несколько блоков CentOS):

  • OSX 10.7, g++ 4.2, boost 1.48, Matlab R2011a (clang++ 2.1 также работает в автономном режиме, не пытался заставить mex использовать clang)
  • древний CentOS, g++ 4.1.2, boost 1.33.1 (отладка и не отладка), Matlab R2010b
  • древний CentOS, g++ 4.1.2, boost 1.40 (отладочные версии не установлены), Matlab R2010b

Вот урезанная версия с таким поведением.

#include <queue>
#include <vector>

#include <boost/thread.hpp>
#include <boost/utility.hpp>

#ifndef NO_MEX
#include "mex.h"
#endif

class Worker : boost::noncopyable {
    boost::mutex &jobs_mutex;
    std::queue<size_t> &jobs;

    boost::mutex &results_mutex;
    std::vector<double> &results;

    public:

    Worker(boost::mutex &jobs_mutex, std::queue<size_t> &jobs,
           boost::mutex &results_mutex, std::vector<double> &results)
        :
            jobs_mutex(jobs_mutex), jobs(jobs),
            results_mutex(results_mutex), results(results)
    {}

    void operator()() {
        size_t i;
        float r;

        while (true) {
            // get a job
            {
                boost::mutex::scoped_lock lk(jobs_mutex);
                if (jobs.size() == 0)
                    return;

                i = jobs.front();
                jobs.pop();
            }

            // do some "work"
            r = rand() / 315.612;

            // write the results
            {
                boost::mutex::scoped_lock lk(results_mutex);
                results[i] = r;
            }
        }
    }
};

std::vector<double> doWork(size_t n) {
    std::vector<double> results;
    results.resize(n);

    boost::mutex jobs_mutex, results_mutex;

    std::queue<size_t> jobs;
    for (size_t i = 0; i < n; i++)
        jobs.push(i);

    Worker w1(jobs_mutex, jobs, results_mutex, results);
    boost::thread t1(boost::ref(w1));

    Worker w2(jobs_mutex, jobs, results_mutex, results);
    boost::thread t2(boost::ref(w2));

    t1.join();
    t2.join();

    return results;
}

#ifdef NO_MEX
int main() {
#else
void mexFunction(int nlhs, mxArray **plhs, int nrhs, const mxArray **prhs) {
#endif
    std::vector<double> results = doWork(10);
    for (size_t i = 0; i < results.size(); i++)
        printf("%g ", results[i]);
    printf("\n");
}

Обратите внимание, что в boost 1.48 я получаю такое же поведение, если изменяю функтор на стандартную функцию и просто передаю boost::refs мьютексам/данным в качестве дополнительных аргументов boost::thread. Однако Boost 1.33.1 этого не поддерживает.


Когда я компилирую его напрямую, он всегда работает нормально — я никогда не видел, чтобы он терпел неудачу в какой-либо ситуации:

$ g++ -o testing testing.cpp -lboost_thread-mt -DNO_MEX
$ ./testing
53.2521 895008 5.14128e+06 3.12074e+06 3.62505e+06 1.48984e+06 320100 4.61912e+06 4.62206e+06 6.35983e+06

Работая с Matlab, я видел много разных поведений после внесения различных изменений в код и т. Д., Хотя никаких изменений, которые действительно имели бы для меня смысл. Но вот что я видел с точным кодом выше:

  • On OSX / boost 1.48:
    • If it's linked to a release-variant boost, I get a segfault trying to access a near-0 address inside of boost::thread::start_thread, being called from t1's constructor.
    • Если он связан с бустом варианта отладки, он навсегда зависает в первом boost::thread::join. Я не совсем уверен, но я думаю, что рабочие потоки фактически завершились к этому моменту (не вижу ничего в info threads, что явно было бы ими).
  • On CentOS / boost 1.33.1 and 1.40:
    • With release boost, I get a segfault in pthread_mutex_lock, being called from the boost::thread::join on t1.
    • С бустом отладки вечно висит в __lll_lock_wait внутри pthread_mutex_lock на одном и том же месте. Как показано ниже, на этом этапе рабочие потоки завершились.

Я не знаю, как еще что-то сделать с segfaults, поскольку они никогда не возникают, когда у меня есть отладочные символы, которые действительно могут сказать мне, что такое нулевой указатель.

В случае зависания навсегда я, кажется, всегда получаю что-то вроде этого, если я прохожу через GDB:

99      Worker w1(jobs_mutex, jobs, results_mutex, results);
(gdb) 
100     boost::thread t1(boost::ref(w1));
(gdb) 
[New Thread 0x47814940 (LWP 19390)]
102     Worker w2(jobs_mutex, jobs, results_mutex, results);
(gdb) 
103     boost::thread t2(boost::ref(w2));
(gdb) 
[Thread 0x47814940 (LWP 19390) exited]
[New Thread 0x48215940 (LWP 19391)]
[Thread 0x48215940 (LWP 19391) exited]
105     t1.join();

Похоже, что оба потока завершены до вызова t1.join(). Итак, я попытался добавить вызов sleep(1) в раздел «выполнение работы» между блокировками; когда я прохожу, потоки завершаются после вызова t1.join(), и он все равно зависает навсегда:

106     t1.join();
(gdb)
[Thread 0x47814940 (LWP 20255) exited]
[Thread 0x48215940 (LWP 20256) exited]
# still hanging

Если я вывожу up в функцию doWork, results заполняется теми же результатами, что и автономная версия на этой машине, так что похоже, что все это происходит.

Я понятия не имею, что является причиной segfaults или сумасшедшего зависания, или почему это всегда работает вне Matlab и никогда внутри, или почему это отличается с / без символов отладки, и я понятия не имею, как действовать в выяснить это. Есть предположения?


По предложению @alanxz я запустил автономную версию кода с помощью инструментов valgrind memcheck, helgrind и DRD:

  • В CentOS с использованием valgrind 3.5 ни один из инструментов не выдает неподавленных ошибок.
  • On OSX using valgrind 3.7:
    • Memcheck doesn't give any non-suppressed errors.
    • У меня Helgrind вылетает при запуске любого бинарного файла (включая, например, valgrind --tool=helgrind ls) в OSX, жалуясь на неподдерживаемую инструкцию.
    • DRD выдает более сотни ошибок.

Ошибки DRD довольно непостижимы для меня, и хотя я читал руководство и так далее, я не могу понять их. Вот первый вариант кода, в котором я закомментировал второй рабочий/поток:

Thread 2:
Conflicting load by thread 2 at 0x0004b518 size 8
   at 0x3B837: void boost::call_once<void (*)()>(boost::once_flag&, void (*)()) (in /usr/local/boost/boost_1_48_0/stage/lib/libboost_thread-mt-d.dylib)
   by 0x2BCD4: boost::detail::set_current_thread_data(boost::detail::thread_data_base*) (in /usr/local/boost/boost_1_48_0/stage/lib/libboost_thread-mt-d.dylib)
   by 0x2BA62: thread_proxy (in /usr/local/boost/boost_1_48_0/stage/lib/libboost_thread-mt-d.dylib)
   by 0x2D88BE: _pthread_start (in /usr/lib/system/libsystem_c.dylib)
   by 0x2DBB74: thread_start (in /usr/lib/system/libsystem_c.dylib)
Allocation context: Data section of r/local/boost/boost_1_48_0/stage/lib/libboost_thread-mt-d.dylib
Other segment start (thread 1)
   at 0x41B4DE: __bsdthread_create (in /usr/lib/system/libsystem_kernel.dylib)
   by 0x2B959: boost::thread::start_thread() (in /usr/local/boost/boost_1_48_0/stage/lib/libboost_thread-mt-d.dylib)
   by 0x100001B54: boost::thread::thread<boost::reference_wrapper<Worker> >(boost::reference_wrapper<Worker>, boost::disable_if<boost::is_convertible<boost::reference_wrapper<Worker>&, boost::detail::thread_move_t<boost::reference_wrapper<Worker> > >, boost::thread::dummy*>::type) (thread.hpp:204)
   by 0x100001434: boost::thread::thread<boost::reference_wrapper<Worker> >(boost::reference_wrapper<Worker>, boost::disable_if<boost::is_convertible<boost::reference_wrapper<Worker>&, boost::detail::thread_move_t<boost::reference_wrapper<Worker> > >, boost::thread::dummy*>::type) (thread.hpp:201)
   by 0x100000B50: doWork(unsigned long) (testing.cpp:66)
   by 0x100000CE1: main (testing.cpp:82)
Other segment end (thread 1)
   at 0x41BBCA: __psynch_cvwait (in /usr/lib/system/libsystem_kernel.dylib)
   by 0x3C0C3: boost::condition_variable::wait(boost::unique_lock<boost::mutex>&) (in /usr/local/boost/boost_1_48_0/stage/lib/libboost_thread-mt-d.dylib)
   by 0x2D28A: boost::thread::join() (in /usr/local/boost/boost_1_48_0/stage/lib/libboost_thread-mt-d.dylib)
   by 0x100000B61: doWork(unsigned long) (testing.cpp:72)
   by 0x100000CE1: main (testing.cpp:82)

Строка 66 — построение потока, а 72 — вызов join; между ними нет ничего, кроме комментариев. Насколько я могу судить, это говорит о том, что существует гонка между этой частью главного потока и инициализацией рабочего потока... но я действительно не понимаю, как это возможно?

Остальные выходные данные DRD здесь; Я ничего не получаю от этого.


person Community    schedule 25.01.2012    source источник
comment
Вы пытались запустить его под valgrind, helgrind или DRD? Это может дать некоторые подсказки о том, что происходит.   -  person alanxz    schedule 30.01.2012
comment
@alanxz Спасибо за предложение, я не знал о хелгринд/DRD. Я добавил некоторые подробности о том, что они говорят на вопрос. Я получаю ошибки DRD на OSX, но понятия не имею, что они означают, несмотря на то, что читал руководство и так далее....   -  person Danica    schedule 31.01.2012
comment
Кто-нибудь пытался настроить @rpath в среде Linux? В настоящее время у меня такая же проблема, я думаю, что мекс должен правильно изолировать свои зависимости.   -  person Raffi    schedule 08.04.2014


Ответы (2)


Вы уверены, что это самый простой случай, когда возникает ошибка сегментации и/или зависание? Если результаты DRD действительно указывают на состояние гонки только между созданием потока и присоединением, похоже, что ваш код может быть не виноват (тем более, что вы на самом деле не используете какие-либо функции, специфичные для mex, а просто работает под mex вызывает ошибку ).

Может быть, попробовать только эту версию:

#include <boost/thread.hpp>

void doNothing() { return; }

void doWork() {
    boost::thread t1(doNothing);
    t1.join();
}

#ifdef NO_MEX
int main() {
#else
#include "mex.h"
void mexFunction(int nlhs, mxArray **plhs, int nrhs, const mxArray **prhs) {
#endif
    doWork();
}

Это определенно не должно вызывать ошибки или зависать ни под mex, ни при компиляции напрямую - поэтому, если это так, это не ваша ошибка, а если нет, возможно, вы можете постепенно сократить расстояние между вашей версией и этой, чтобы найти ошибку- вызывающее дополнение.

person Community    schedule 31.01.2012
comment
Да, эта версия тоже segfaults. Я думаю, потоки mex и boost просто не работают вместе. Думаю, пора сообщить об этом как об ошибке и портировать на pthreads.... - person Danica; 31.01.2012

В вашем коде есть точка отказа: когда любой поток задерживается более чем на 2 секунды, вызов timed_lock в конструкторе блокировки может истечь, мьютекс не захвачен, и вы получаете доступ к защищенному структура в любом случае. Если вы используете временные мьютексы, вам придется проверить, действительно ли блокировка заблокировала мьютекс или просто истекло время ожидания. Это можно проверить, вызвав метод блокировки owns_lock().

Я не вижу никакой мотивации для временных мьютексов здесь, и вы упоминаете «после удаления материала временного потока», но я все еще подозреваю, что здесь виновата ошибка тайм-аута мьютекса. Эта ошибка все еще возникает, когда вы заменяете timed_mutex на обычный mutex?

person thiton    schedule 26.01.2012
comment
Первоначально я использовал обычный mutex; материал timed_mutex был добавлен после того, как увидел поведение взаимоблокировки. По какой-то причине я подумал, что timed_mutex сгенерировал исключение, если он не получил блокировку, хотя, наверное, я не знаю, почему я так подумал. Кроме того, когда происходит сегментация, это происходит немедленно, а не через 2 секунды. - person Danica; 27.01.2012
comment
Чтобы быть явным: да, я все еще получаю segfaults / зависания, когда я заменяю timed_mutex простым mutex (и, очевидно, удаляю аргумент времени и т. д.). Теперь я вижу согласованность в проблеме между моей машиной с OSX Boost 1.48 и машиной с CentOS Boost 1.33.1, которой у меня раньше не было, хотя ... я исследую это немного более тщательно, а затем отредактирую вопрос . - person Danica; 27.01.2012
comment
Я исправил эту проблему и отредактировал вопрос. Похоже, что блокировка не была проблемой, поскольку она дает сбой до того, как доберется туда в OSX + new boost, а в CentOS + old boost потоки успешно завершаются (по крайней мере, когда я их отлаживаю), но либо сбой, либо зависание когда просят присоединиться. - person Danica; 27.01.2012