Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bug in Python? threading.Thread.start() does not always return

I have a tiny Python script which (in my eyes) makes threading.Thread.start() behave unexpectedly since it does not return immediately.

Inside a thread I want to call a method from a boost::python based object which will not return immediately.

To do so I wrap the object/method like this:

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!!

And everything works fine as long as I run some code before my_cpp_object.non_terminating_fn(). If I don't, .start() will block the same way as calling .run() directly would.

Printing just a line before calling the boost::python function is not enough, but e.g. printing two lines or calling time.sleep() makes start() return immediately as expected.

Can you explain this behavior? How would I avoid this (apart from calling sleep() before calling a boost::python function)?

like image 325
frans Avatar asked Jul 16 '15 14:07

frans


People also ask

Can thread stop itself Python?

The python module threading has an object Thread to be used to run processes and functions in a different thread. This object has a start method, but no stop method.

What does thread start do in Python?

Once a thread object is created, its activity must be started by calling the thread's start() method. This invokes the run() method in a separate thread of control. Once the thread's activity is started, the thread is considered 'alive'.

How do I stop a thread and start it again in Python?

Calling the start() function on a terminated thread will result in a RuntimeError indicating that threads can only be started once. Instead, to restart a thread in Python, you must create a new instance of the thread with the same configuration and then call the start() function.

Is start a method of Python threading interface?

start() − The start() method starts a thread by calling the run method. join([time]) − The join() waits for threads to terminate. isAlive() − The isAlive() method checks whether a thread is still executing. getName() − The getName() method returns the name of a thread.


1 Answers

This behavior is (as in most cases when you believe in a bug in an interpreter/compiler) not a bug in Python but a race condition covering the behavior you have to expect because of the Python GIL (also discussed here).

As soon as the non-Python function my_cpp_object.non_terminating_fn() has been started the GIL doesn't get released until it returns and keeps the interpreter from executing any other command.

So time.sleep(1) doesn't help here anyway because the code following my_cpp_object.non_terminating_fn() would not be executed until the GIL gets released.

In case of boost::python and of course in case you can modify the C/C++ part you can release the GIL manually as described here.

A small example (from the link above) could look like this (in the boost::python wrapper code)

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();
}
like image 200
frans Avatar answered Nov 15 '22 10:11

frans