我有一个C++
应用程序,它从多个线程调用Python
函数。
一切正常,直到我尝试从OpenCV
内部使用Python
函数为止:
C++
线程中调用,它将永远锁定,等待互斥体被释放基本上我有两个文件:
script.py:
import cv2
def foo():
print('foo_in')
cv2.imread('sample.jpg')
print('foo_out')
main.cpp:
#include <pthread.h>
#include <pybind11/embed.h>
pybind11::handle g_main;
void* foo(void*)
{
g_main.attr("foo")();
}
int main()
{
pybind11::scoped_interpreter guard;
pybind11::eval_file("script.py");
g_main = pybind11::module::import("__main__");
foo(nullptr);
pthread_t thread;
pthread_create(&thread, nullptr, &foo, nullptr);
pthread_join(thread, nullptr);
return 0;
}
当我执行C ++代码段时,我得到:
foo_in
foo_out
foo_in
...然后它永远卡住了。
您可以看到,对cv2.imread
的第一次调用返回了,但第二次调用没有返回(在另一个线程中调用了)。
当我strace
线程PID时,我得到以下几行:
futex(0x7fe7e6b3e364, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 13961, {1550596187, 546432000}, ffffffff) = -1 ETIMEDOUT (Connection timed out)
futex(0x7fe7e6b3e3e0, FUTEX_WAKE_PRIVATE, 1) = 0
...一遍又一遍地打印,这让我觉得线程正在等待释放互斥体。
我进一步尝试通过使用gdb
的回溯来了解发生了什么:
#0 pthread_cond_timedwait@@GLIBC_2.3.2 () at ../sysdeps/unix/sysv/linux/x86_64/pthread_cond_timedwait.S:225
#1 0x00007fe7e667948f in ?? () from /usr/lib/x86_64-linux-gnu/libpython3.5m.so.1.0
#2 0x00007fe7e6679979 in PyEval_RestoreThread () from /usr/lib/x86_64-linux-gnu/libpython3.5m.so.1.0
#3 0x00007fe7e669968b in PyGILState_Ensure () from /usr/lib/x86_64-linux-gnu/libpython3.5m.so.1.0
#4 0x00007fe7e3fa7635 in PyEnsureGIL::PyEnsureGIL (this=<synthetic pointer>) at <opencv>/modules/python/src2/cv2.cpp:83
#5 NumpyAllocator::deallocate (this=<optimized out>, u=0x7fe7a80008c0) at <opencv>/modules/python/src2/cv2.cpp:208
#6 0x00007fe7d88e17c2 in cv::MatAllocator::unmap (this=<optimized out>, u=<optimized out>) at <opencv>/modules/core/src/matrix.cpp:18
#7 0x00007fe7e3fa7dc8 in cv::Mat::release (this=0x7fe7ae8018e0) at <opencv>/modules/core/include/opencv2/core/mat.inl.hpp:808
#8 cv::Mat::~Mat (this=0x7fe7ae8018e0, __in_chrg=<optimized out>) at <opencv>/modules/core/include/opencv2/core/mat.inl.hpp:694
#9 pyopencv_from<cv::Mat> (m=...) at <opencv>/modules/python/src2/cv2.cpp:451
#10 0x00007fe7e3faa08c in pyopencv_cv_imread (args=<optimized out>, kw=<optimized out>) at <opencv>/build/modules/python_bindings_generator/pyopencv_generated_funcs.h:10588
#11 0x00007fe7e6575049 in PyCFunction_Call () from /usr/lib/x86_64-linux-gnu/libpython3.5m.so.1.0
#12 0x00007fe7e66811c5 in PyEval_EvalFrameEx () from /usr/lib/x86_64-linux-gnu/libpython3.5m.so.1.0
#13 0x00007fe7e6711cbc in ?? () from /usr/lib/x86_64-linux-gnu/libpython3.5m.so.1.0
#14 0x00007fe7e6711d93 in PyEval_EvalCodeEx () from /usr/lib/x86_64-linux-gnu/libpython3.5m.so.1.0
#15 0x00007fe7e6599ac8 in ?? () from /usr/lib/x86_64-linux-gnu/libpython3.5m.so.1.0
#16 0x00007fe7e664e55e in PyObject_Call () from /usr/lib/x86_64-linux-gnu/libpython3.5m.so.1.0
#17 0x00007fe7e6710947 in PyEval_CallObjectWithKeywords () from /usr/lib/x86_64-linux-gnu/libpython3.5m.so.1.0
#18 0x00000000004369de in pybind11::detail::simple_collector<(pybind11::return_value_policy)1>::call (this=0x7fe7ae801e80, ptr=0x7fe7e6eaef28) at <pybind11>/pybind11/cast.h:1953
#19 0x00000000004334f3 in pybind11::detail::object_api<pybind11::detail::accessor<pybind11::detail::accessor_policies::str_attr> >::operator()<(pybind11::return_value_policy)1> (this=0x7fe7ae801ed0)
at <pybind11>/pybind11/cast.h:2108
#20 0x0000000000424336 in foo () at main.cpp:11
我尝试将python解释器初始化移到foo
函数中,然后它起作用了(我只需要删除对foo
的第一次调用,因为每个应用程序解释器只能初始化一次)。 / p>
这使我认为cv2.imread
函数仅在初始化解释器的同一线程中调用时返回。
如果我用任何其他OpenCV函数替换对cv2.imread
的调用,也会发生同样的情况。
我在cv2.imwrite
和cv2.projectPoints
上进行了测试。
关于正在发生的事情以及如何解决它,同时仍然能够从不同线程调用OpenCV函数的任何想法?
答案 0 :(得分:0)
因此,事实证明问题出在我使用Python指令而不拿着GIL(全局解释器锁)的问题。 GIL首先由初始化解释器的线程保存,并且必须显式释放GIL,其他线程才能获取它。
执行锁定在cv2.imread
指令而不是print('foo_in')
指令上的原因是,当从C ++调用时,Python解释器不能确保它保持GIL(这意味着任何纯Python指令以线程不安全的方式执行)。但是,由cv2.*
指令调用的C ++代码确实可以确保在执行之前保持GIL,因此可以锁定。
我通过明确的GIL发布和获取解决了该问题:
main.cpp
#include <pthread.h>
#include <pybind11/embed.h>
pybind11::handle g_main;
void* foo(void*)
{
pybind11::gil_scoped_acquire gil;
g_main.attr("foo")();
}
int main()
{
pybind11::scoped_interpreter guard;
pybind11::eval_file("../script.py");
g_main = pybind11::module::import("__main__");
pybind11::gil_scoped_release nogil;
foo(nullptr);
pthread_t thread;
pthread_create(&thread, nullptr, &foo, nullptr);
pthread_join(thread, nullptr);
return 0;
}
现在一切正常,我确实得到了预期的输出:
foo_in
foo_out
foo_in
foo_out