Python中的错误?threading.Thread.start() 并不总是返回

fra*_*ans 6 multithreading boost-python python-3.x

我有一个很小的 ​​Python 脚本,它(在我看来)threading.Thread.start()由于它不会立即返回而导致行为出乎意料。

在一个线程中,我想从一个boost::python不会立即返回的基于对象调用一个方法。

为此,我像这样包装对象/方法:

import threading
import time
import my_boostpython_lib

my_cpp_object = my_boostpython_lib.my_cpp_class()

def some_fn():
    # has to be here - otherwise .start() does not return
    # time.sleep(1)  
    my_cpp_object.non_terminating_fn() # blocks

print("%x: 1" % threading.get_ident())
threading.Thread(target=some_fn).start()
print("%x: 2" % threading.get_ident())  # will not always be called!!
Run Code Online (Sandbox Code Playgroud)

只要我 my_cpp_object.non_terminating_fn(). 如果我不这样做,.start()将像.run()直接调用一样阻塞。

在调用boost::python函数之前只打印一行是不够的,但是例如打印两行或调用time.sleep()使start()按预期立即返回。

你能解释一下这种行为吗?我将如何避免这种情况(除了sleep()在调用boost::python函数之前调用)?

fra*_*ans 5

这种行为(在大多数情况下,当您相信解释器/编译器中存在错误时)不是 Python 中的错误,而是一种竞争条件,涵盖了由于 Python GIL (也在此处讨论)而必须预期的行为。

一旦非 Python 函数my_cpp_object.non_terminating_fn()启动,GIL 就不会被释放,直到它返回并阻止解释器执行任何其他命令。

所以time.sleep(1)无论如何在这里没有帮助,因为my_cpp_object.non_terminating_fn()在 GIL 被释放之前,下面的代码不会被执行。

当然,如果您可以修改 C/C++ 部分,您可以按照此处boost::python所述手动释放 GIL 。

一个小例子(来自上面的链接)可能看起来像这样(在 boost::python 包装器代码中)

class scoped_gil_release {
public:
    inline scoped_gil_release() {
        m_thread_state = PyEval_SaveThread();
    }

    inline ~scoped_gil_release() {
        PyEval_RestoreThread(m_thread_state);
        m_thread_state = NULL;
    }

private:
    PyThreadState * m_thread_state;
};

int non_terminating_fn_wrapper() {
    scoped_gil_release scoped;
    return non_terminating_fn();
}
Run Code Online (Sandbox Code Playgroud)