当从matlab使用boost::线程时,挂起和/或段错误,而不是直接调用时

hang and/or segfault when using boost::threads from matlab, not when called directly

本文关键字:错误 调用 boost 使用 matlab 线程 挂起 当从 段错误      更新时间:2023-10-16

问题是什么,以防人们有类似的问题:在与Mathworks支持人员进行了一些讨论之后,结果发现这是系统boost和Matlab附带的boost库之间的冲突:当我使用系统boost头并与(旧的)Matlab boost库链接时,它会断开。当我编译并动态链接到系统boost时,但是它动态加载了Matlab boost库,它永远挂起了。

静态链接到系统boost工作,就像下载Matlab附带的boost版本的正确头文件并使用它们进行编译一样。当然,Mac版本的Matlab在文件名中没有版本号,尽管Linux和Windows版本有。R2011b使用boost 1.44,供参考。


我有一些多线程代码,当它直接编译时工作良好,但是当它从Matlabmex接口调用时,会出现分段错误和/或死锁。我不知道不同的环境是否暴露了我的代码中的缺陷,还是什么,但我不能弄清楚....

我在三种机器配置上运行这个(尽管有几个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运行,我看到了很多不同的行为对代码等做出不同的调整后,虽然没有变化,实际上对我有任何意义。下面是我看到的上面的代码:

  • 在OSX/boost 1.48:
    • 如果它链接到一个版本变体boost,我得到一个段错误试图访问boost::thread::start_thread内部的近0地址,从t1的构造函数调用。
    • 如果它被链接到一个debug-variant boost,它永远挂在第一个boost::thread::join。我不完全确定,但我认为工作线程实际上已经完成在这一点上(没有看到任何在info threads,显然是他们)。
  • 在CentOS/boost 1.33.1和1.40:
    • 与发布boost,我得到pthread_mutex_lock段故障,从t1上的boost::thread::join调用。
    • 与调试boost,它永远挂在__lll_lock_waitpthread_mutex_lock在同一个地方。如下所示,工作线程已经完成。

我不知道如何处理段错误,因为当我有调试符号可以告诉我空指针是什么时,它们永远不会发生。

在永远挂起的情况下,如果我在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将被填充与这台机器上独立版本打印的结果相同的结果,因此看起来就像所经历的一切。

我不知道是什么原因导致了段故障或疯狂的悬挂性,或者为什么它总是在Matlab外部工作而不在内部,或者为什么它与/不调试符号不同,我不知道如何继续解决这个问题。任何想法吗?


在@alanxz的建议下,我在valgrind的memcheck, helgrind和DRD工具下运行了独立版本的代码:

  • 在使用valgrind 3.5的CentOS上,没有一个工具给出任何非抑制错误。
  • 在OSX使用valgrind 3.7:
    • Memcheck不给出任何非抑制错误。
    • 当我在OSX上运行任何二进制文件(包括例如valgrind --tool=helgrind ls)时,Helgrind崩溃,抱怨不支持的指令。
  • DRD给出超过100个错误。

DRD错误对我来说是相当难以理解的,尽管我已经阅读了手册等,但我无法理解它们。下面是第一个,关于我注释掉第二个worker/线程的代码版本:

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的其余输出在这里;我什么也得不到。

你确定这是最简单的segfault和/或挂起的情况吗?如果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或直接编译下发生分段故障或挂起-所以如果它发生了,这不是你的错误,如果它没有,也许你可以逐渐缩小你的版本和这个版本之间的距离,以找到导致错误的添加。

在您的代码中有一个故障点:当任何线程延迟超过2秒时,锁构造函数中的timed_lock调用可能超时,互斥锁没有获得,并且您仍然访问受保护的结构。如果使用定时互斥锁,则必须测试锁是否真的锁定了互斥锁,还是仅仅是超时了。这可以通过调用锁的owns_lock()方法来检查。

我看不出这里使用定时互斥锁的任何动机,你提到"在取出定时线程的东西之后",但我仍然怀疑这个互斥锁超时错误是错误的。当你用普通的mutex替换timed_mutex时,这个bug还会发生吗?