6 Stimmen

Hängen und/oder Segfault bei Verwendung von boost::threads aus Matlab, nicht bei direktem Aufruf

Was das Problem war, für den Fall, dass andere ein ähnliches Problem haben: Nach einigen Diskussionen mit dem Mathworks-Support stellte sich heraus, dass es sich um einen Konflikt zwischen den System-Boost- und den Matlab-Boost-Bibliotheken handelte: Wenn ich mit den System-Boost-Headern kompilierte und mit den (älteren) Matlab-Boost-Bibliotheken verknüpfte, kam es zu einem Segfault. Wenn ich mit System-Boost kompiliert und dynamisch gelinkt habe, aber dann die Matlab-Boost-Bibliotheken dynamisch geladen habe, blieb es für immer hängen.

Statisches Linken zu System-Boost funktioniert, ebenso wie das Herunterladen der richtigen Header für die Version von Boost, mit der Matlab ausgeliefert wird, und das Kompilieren mit diesen. Natürlich haben die Mac-Builds von Matlab keine Versionsnummern in ihren Dateinamen, aber die Linux- und vermutlich auch die Windows-Builds schon. R2011b verwendet boost 1.44, als Referenz.


Ich habe einige multithreaded Code, der gut funktioniert, wenn es direkt kompiliert wird, aber segfaults und/oder Deadlocks, wenn es von einem Matlab aufgerufen wird mex Schnittstelle. Ich weiß nicht, ob die unterschiedliche Umgebung einen Fehler in meinem Code offenbart, oder was, aber ich kann es nicht herausfinden....

Ich lasse dies auf drei Rechnerkonfigurationen laufen (obwohl es mehrere CentOS-Kisten gibt):

  • OSX 10.7, g++ 4.2, boost 1.48, Matlab R2011a (clang++ 2.1 funktioniert auch für Standalone, habe nicht versucht, mex dazu zu bringen, clang zu verwenden)
  • altes CentOS, g++ 4.1.2, boost 1.33.1 (debug und nicht debug), Matlab R2010b
  • altes CentOS, g++ 4.1.2, boost 1.40 (keine Debug-Versionen installiert), Matlab R2010b

Hier ist eine abgespeckte Version mit diesem Verhalten.

#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");
}

Beachten Sie, dass ich unter boost 1.48 das gleiche Verhalten erhalte, wenn ich den Funktor in eine Standardfunktion umwandle und einfach boost::ref s zu den Mutexen/Daten als zusätzliche Argumente für boost::thread . Boost 1.33.1 unterstützt dies jedoch nicht.


Wenn ich es direkt kompiliere, läuft es immer gut - ich habe noch nie erlebt, dass es in irgendeiner Situation fehlschlägt:

$ 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

Bei der Ausführung in Matlab habe ich viele verschiedene Verhaltensweisen gesehen, nachdem ich verschiedene Änderungen am Code usw. vorgenommen habe, allerdings keine Änderungen, die für mich wirklich Sinn ergeben. Aber hier ist, was ich mit dem genauen Code oben gesehen habe:

  • Unter OSX / boost 1.48:
    • Wenn er mit einer Release-Variante von boost verknüpft ist, erhalte ich einen Segfault, wenn ich versuche, auf eine Near-0-Adresse innerhalb von boost::thread::start_thread , die von t1 Konstrukteur.
    • Wenn er mit einer Debug-Variante von boost verknüpft ist, bleibt er in der ersten Phase ewig hängen. boost::thread::join . Ich bin mir nicht ganz sicher, aber ich denke, dass die Worker-Threads zu diesem Zeitpunkt tatsächlich abgeschlossen sind (ich sehe nichts in info threads das sind sie offensichtlich).
  • Unter CentOS / boost 1.33.1 und 1.40:
    • Mit Release Boost erhalte ich einen Segfault in pthread_mutex_lock , die vom boost::thread::join en t1 .
    • Mit dem Debugging-Boost hängt es ewig in __lll_lock_wait innerhalb pthread_mutex_lock an der gleichen Stelle. Wie unten gezeigt, sind die Arbeits-Threads zu diesem Zeitpunkt abgeschlossen.

Ich weiß nicht, wie man etwas mehr mit den segfaults zu tun, da sie nie auftreten, wenn ich Debugging-Symbole, die mir tatsächlich sagen können, was der Null-Zeiger ist.

Im Fall von "hanging-forever" scheine ich immer so etwas zu bekommen, wenn ich in GDB durchlaufe:

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();

Das sieht so aus, als ob beide Threads abgeschlossen sind, bevor der Aufruf von t1.join() . Also habe ich versucht, eine sleep(1) Aufruf im Abschnitt "doing work" zwischen den Sperren; wenn ich durchlaufe, beenden die Threads nach dem Aufruf von t1.join() und es hängt immer noch ewig fest:

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

Wenn ich up hinaus zum doWork Funktion, results wird mit denselben Ergebnissen gefüllt, die die Standalone-Version auf diesem Rechner druckt, es sieht also so aus, als würde alles funktionieren.

Ich habe keine Ahnung, was entweder die segfaults oder die verrückte hanging-ness verursacht, oder warum es ist, dass es immer außerhalb von Matlab und nie innerhalb funktioniert, oder warum es mit/ohne Debugging-Symbole unterschiedlich ist, und ich habe keine Ahnung, wie man in herauszufinden, diese gehen. Hat jemand eine Idee?


Auf Anregung von @alanxz habe ich die Standalone-Version des Codes mit den Tools memcheck, helgrind und DRD von valgrind ausgeführt:

  • Unter CentOS, das valgrind 3.5 verwendet, gibt keines der Tools einen nicht unterdrückten Fehler aus.
  • Unter OSX mit valgrind 3.7:
    • Memcheck meldet keine nicht unterdrückten Fehler.
    • Helgrind stürzt bei mir ab, wenn es mit einer beliebigen Binärdatei ausgeführt wird (einschließlich z.B. valgrind --tool=helgrind ls ) unter OSX und beschwert sich über eine nicht unterstützte Anweisung.
    • DRD gibt über hundert Fehler an.

Die DRD-Fehler sind für mich ziemlich undurchschaubar, und obwohl ich das Handbuch und so weiter gelesen habe, kann ich mir keinen Reim darauf machen. Hier ist der erste, in einer Version des Codes, in der ich den zweiten Worker/Thread auskommentiert habe:

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)

Zeile 66 ist die Konstruktion des Gewindes, und 72 ist die join Anruf; dazwischen gibt es nichts als Kommentare. Soweit ich das beurteilen kann, heißt das, dass es einen Wettlauf zwischen diesem Teil des Master-Threads und der Initialisierung des Worker-Threads gibt... aber ich verstehe nicht wirklich, wie das möglich ist?

Der Rest der Ausgabe von DRD ist hier Ich habe keinen Nutzen davon.

1voto

Nick Felt Punkte 345

Sind Sie sicher, dass dies der einfachste Fall ist, bei dem es zu Fehlern und/oder Hängern kommt? Wenn die Ergebnisse von DRD auf eine Wettlaufbedingung nur zwischen der Thread-Erstellung und dem Beitritt hinweisen, klingt es, als ob Ihr Code nicht fehlerhaft sein könnte (vor allem, da Sie eigentlich keine mex -spezifischen Funktionen, sondern läuft nur unter mex den Fehler auslöst).

Versuchen Sie vielleicht nur diese Version:

#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();
}

Dies sollte definitiv nicht segfault oder hängen entweder unter mex oder direkt kompiliert - wenn es also so ist, ist es nicht Ihr Fehler, und wenn es nicht so ist, können Sie vielleicht den Abstand zwischen Ihrer Version und dieser schrittweise verringern, um den fehlerverursachenden Zusatz zu finden.

0voto

thiton Punkte 35046

Es gibt eine Schwachstelle in Ihrem Code: Wenn ein Thread um mehr als 2 Sekunden verzögert wird, wird die timed_lock Aufruf im Lock-Konstruktor eine Zeitüberschreitung verursachen kann, wird der Mutex no erworben, und Sie greifen trotzdem auf die geschützte Struktur zu. Wenn Sie zeitgesteuerte Mutexe verwenden, müssen Sie prüfen, ob die Sperre den Mutex tatsächlich gesperrt hat oder ob sie lediglich abgelaufen ist. Dies kann überprüft werden, indem man die Locks' owns_lock() Methode.

Ich sehe keine Motivation für die zeitgesteuerten Mutexe hier, und Sie erwähnen "nach dem Entfernen der zeitgesteuerten Threads", aber ich vermute immer noch, dass dieser Mutex-Timeout-Bug hier der Fehler ist. Tritt dieser Fehler immer noch auf, wenn Sie timed_mutex mit einfacher mutex ?

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X