Python PyGILState_{Ensure/Release} 返回 C++ 时导致段错误
更新 看来在调用 PyGILState_Ensure() 之前加上 PyEval_InitThreads() 就能解决问题。在我急于搞清楚事情的时候,错误地把我的“卡住”归咎于 PyEval_InitThreads()。
不过,在阅读了一些 Python 的文档后,我在想这是否真的是正确的解决办法。
在不知道哪个线程(如果有的话)当前持有全局解释器锁的情况下,调用这个函数是不安全的。
首先,我正在处理一些修改过的 GNU Radio 代码,特别是一个修改过的 gr_bin_statistics_f 块。现在,有一个(虽然是旧的)错误报告,几乎描述了我遇到的确切情况。
http://gnuradio.org/redmine/issues/show/199
在这个错误报告中提到的 usrp_spectrum_sense.py 会调用 gr_bin_statistics_f(C++),然后定期回调 Python 代码来重新调谐 USRP(无线电)。
当调用 Python 代码时,会发生以下情况:
PyGILState_STATE d_gstate;
d_gstate = PyGILState_Ensure();
// call python code
PyGILState_Release(d_gstate);
所以,一旦我们从 Python 代码返回,调用 PyGILState_Release(d_gstate) 时就会出现段错误。虽然我的代码和原始的 gr_bin_statistics_f 之间有一些差异,但似乎没有任何东西与此相关。
我听说在 PyGILState_Ensure() 之前调用 PyEval_InitThreads() 对一些人解决了这个问题,但这反而让我程序卡住了。
有没有人能给我一些启示?还是说该是时候给 GNU Radio 邮件列表发个消息了?
我在 Fedora 14 x86_64 上使用 Python2.7。
这是 GDB 的回溯信息:
(gdb) c
Continuing.
[New Thread 0x7fabd3a8d700 (LWP 23969)]
[New Thread 0x7fabd328c700 (LWP 23970)]
[New Thread 0x7fabd2a8b700 (LWP 23971)]
[New Thread 0x7fabd228a700 (LWP 23972)]
[New Thread 0x7fabd1a89700 (LWP 23973)]
[New Thread 0x7fabd1288700 (LWP 23974)]
[New Thread 0x7fabd0a87700 (LWP 23975)]
[New Thread 0x7fabbbfff700 (LWP 23976)]
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fabbbfff700 (LWP 23976)]
0x00000036b3e0db00 in sem_post () from /lib64/libpthread.so.0
(gdb) bt
#0 0x00000036b3e0db00 in sem_post () from /lib64/libpthread.so.0
#1 0x00000036c1317679 in PyThread_release_lock () from /usr/lib64/libpython2.7.so.1.0
#2 0x00007fabd6159c1f in ~ensure_py_gil_state (this=0x2dc6fc0, x=887000000)
at gnuradio_swig_py_general.cc:5593
#3 gr_py_feval_dd::calleval (this=0x2dc6fc0, x=887000000) at gnuradio_swig_py_general.cc:5605
#4 0x00007fabd77c4b6e in gr_noise_level_f::tune_window (this=0x2db3ca0,
target_freq=) at gr_noise_level_f.cc:97
#5 0x00007fabd77c554b in gr_noise_level_f::work (this=0x2db3ca0, noutput_items=7,
input_items=, output_items=)
at gr_noise_level_f.cc:115
#6 0x00007fabd7860714 in gr_sync_block::general_work (this=0x2db3ca0,
noutput_items=, ninput_items=,
input_items=, output_items=) at gr_sync_block.cc:64
#7 0x00007fabd7846ce4 in gr_block_executor::run_one_iteration (this=0x7fabbbffed90)
at gr_block_executor.cc:299
#8 0x00007fabd7864332 in gr_tpb_thread_body::gr_tpb_thread_body (this=0x7fabbbffed90, block=...)
at gr_tpb_thread_body.cc:49
#9 0x00007fabd785cce7 in operator() (function_obj_ptr=...) at gr_scheduler_tpb.cc:42
#10 operator() (function_obj_ptr=...)
at /home/tja/Research/energy/detector/gnuradio-3.3.0/gruel/src/include/gruel/thread_body_wrapper.h:49
#11 boost::detail::function::void_function_obj_invoker0, void>::invoke (function_obj_ptr=...) at /usr/include/boost/function/function_template.hpp:153
---Type to continue, or q to quit---
#12 0x00007fabd74914ef in operator() (this=)
at /usr/include/boost/function/function_template.hpp:1013
#13 boost::detail::thread_data >::run (this=)
at /usr/include/boost/thread/detail/thread.hpp:61
#14 0x00007fabd725ca55 in thread_proxy () from /usr/lib64/libboost_thread-mt.so.1.44.0
#15 0x00000036b3e06d5b in start_thread () from /lib64/libpthread.so.0
#16 0x00000036b3ae4a7d in clone () from /lib64/libc.so.6
(gdb)
谢谢你的关注!
2 个回答
我也遇到过这个问题。关于CPython中线程的文档,实话说,信息很少,甚至有些地方不太清楚。
简单来说,你需要做以下几件事:
在你的主线程中,在启动其他线程之前,你需要调用PyEval_InitThreads()
。一个合适的时机是在你调用PyInitialize()
之后。
调用PyEval_InitThreads()
不仅会初始化Python解释器的线程状态,它还会自动获取全局解释器锁(Global Interpreter Lock)。这意味着,在其他线程中调用PyGILEnsure_State()
之前,你需要释放这个锁,否则你的程序会卡住。你可以使用PyEval_ReleaseLock()
这个函数来释放锁。
所以,总的来说,在你的主线程中,在启动其他线程之前,你需要这样做:
PyInitialize();
PyEval_InitThreads();
PyEval_ReleaseLock();
然后,在任何额外的线程中,每次使用Python API时,你需要这样做:
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
/* ... some code that does things with Python ... */
PyGILState_Release(gstate);
在Python中,主线程需要先做好一些准备工作,才能让其他子线程进行调用。
如果主线程是一个嵌入了Python的应用程序,那么在调用完Py_Initialize()
之后,应该立刻调用PyEval_InitThreads()
。
如果主线程是Python解释器本身(在这里似乎就是这种情况),那么使用多线程扩展模块的模块应该尽早包含“import threading”,以确保在创建任何子线程之前,PyEval_InitThreads()
能够正确调用。