当由C ++线程程序调用的Python脚本本身调用OpenCV函数时,其锁定

时间:2019-02-19 18:18:53

标签: python c++ multithreading opencv

我有一个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.imwritecv2.projectPoints上进行了测试。

关于正在发生的事情以及如何解决它,同时仍然能够从不同线程调用OpenCV函数的任何想法?

1 个答案:

答案 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