2015-05-14 2 views
3

Рассмотрим следующее простое расширение python. Когда start()-ed, Foo будет просто добавить следующий последовательный целое в py::list, один раз в секунду:Повышенная ошибка сегментации резьбы на python

#include <boost/python.hpp> 
#include <thread> 
#include <atomic> 

namespace py = boost::python; 

struct Foo { 
    Foo() : running(false) { } 
    ~Foo() { stop(); } 

    void start() { 
     running = true; 
     thread = std::thread([this]{ 
      while(running) { 
       std::cout << py::len(messages) << std::end; 
       messages.append(py::len(messages)); 
       std::this_thread::sleep_for(std::chrono::seconds(1)); 
      } 
     }); 
    } 

    void stop() { 
     if (running) { 
      running = false; 
      thread.join(); 
     } 
    } 

    std::thread thread; 
    py::list messages; 
    std::atomic<bool> running; 
}; 

BOOST_PYTHON_MODULE(Foo) 
{ 
    PyEval_InitThreads(); 

    py::class_<Foo, boost::noncopyable>("Foo", 
     py::init<>()) 
     .def("start", &Foo::start) 
     .def("stop", &Foo::stop) 
    ; 
} 

Учитывая вышеизложенное, следующий простой питон скрипт возвращает ошибку сегментации все время, никогда даже и не печатать ничего:

>>> import Foo 
>>> f = Foo.Foo() 
>>> f.start() 
>>> Segmentation fault (core dumped) 

с ядром, указывая на:

namespace boost { namespace python { 

    inline ssize_t len(object const& obj) 
    { 
     ssize_t result = PyObject_Length(obj.ptr()); 
     if (PyErr_Occurred()) throw_error_already_set(); // <== 
     return result; 
    } 

}} // namespace boost::python 

Где:

(gdb) inspect obj 
$1 = (const boost::python::api::object &) @0x62d368: {<boost::python::api::object_base> = {<boost::python::api::object_operators<boost::python::api::object>> = {<boost::python::def_visitor<boost::python::api::object>> = {<No data fields>}, <No data fields>}, m_ptr = []}, <No data fields>} 
(gdb) inspect obj.ptr() 
$2 = [] 
(gdb) inspect result 
$3 = 0 

Почему это происходит при запуске в потоке? obj выглядит хорошо, result устанавливается правильно. Почему происходит PyErr_Occurred()? Кто это устанавливает?

+2

* было бы обидно, если бы такой отличный ответ получил только один взлет * ... Больше нет! –

ответ

9

Короче говоря, существует мьютекс вокруг интерпретатора CPython, известный как Global Interpreter Lock (GIL). Этот мьютекс предотвращает параллельные операции над объектами Python. Таким образом, в любой момент времени максимум одного потока, тот, который приобрел GIL, разрешен для выполнения операций над объектами Python. Когда присутствуют несколько потоков, вызывать код Python, не удерживая GIL, приводит к неопределенному поведению.

Потоки C или C++ иногда упоминаются как чужие потоки в документации Python. У интерпретатора Python нет возможности управлять чужой нитью. Поэтому чужие потоки отвечают за управление GIL, чтобы разрешать параллельное или параллельное выполнение с потоками Python. Имея это в виду, давайте рассмотрим исходный код:

while (running) { 
    std::cout << py::len(messages) << std::endl;   // Python 
    messages.append(py::len(messages));     // Python 
    std::this_thread::sleep_for(std::chrono::seconds(1)); // No Python 
} 

Как было отмечено выше, только две из трех линий в теле нити должны работать в то время как поток владеет GIL. Один из распространенных способов справиться с этим - использовать классы RAII, чтобы помочь управлять GIL. Например, со следующим классом gil_lock, когда создается объект gil_lock, вызывающий поток получит GIL. Когда объект gil_lock разрушен, он освобождает GIL.

/// @brief RAII class used to lock and unlock the GIL. 
class gil_lock 
{ 
public: 
    gil_lock() { state_ = PyGILState_Ensure(); } 
    ~gil_lock() { PyGILState_Release(state_); } 
private: 
    PyGILState_STATE state_; 
}; 

Корпус нити затем может использовать явный объем, чтобы контролировать срок службы замка.

while (running) { 
    // Acquire GIL while invoking Python code. 
    { 
    gil_lock lock; 
    std::cout << py::len(messages) << std::endl; 
    messages.append(py::len(messages)); 
    } 
    // Release GIL, allowing other threads to run Python code while 
    // this thread sleeps. 
    std::this_thread::sleep_for(std::chrono::seconds(1)); 
} 

Вот полный пример, основанный на исходном коде, который demonstrates программа работает нормально, как только GIL явно удалось:

#include <thread> 
#include <atomic> 
#include <iostream> 
#include <boost/python.hpp> 

/// @brief RAII class used to lock and unlock the GIL. 
class gil_lock 
{ 
public: 
    gil_lock() { state_ = PyGILState_Ensure(); } 
    ~gil_lock() { PyGILState_Release(state_); } 
private: 
    PyGILState_STATE state_; 
}; 

struct foo 
{ 
    foo() : running(false) {} 
    ~foo() { stop(); } 

    void start() 
    { 
    namespace python = boost::python; 
    running = true; 
    thread = std::thread([this] 
     { 
     while (running) 
     { 
      { 
      gil_lock lock; // Acquire GIL. 
      std::cout << python::len(messages) << std::endl; 
      messages.append(python::len(messages)); 
      } // Release GIL. 
      std::this_thread::sleep_for(std::chrono::seconds(1)); 
     } 
     }); 
    } 

    void stop() 
    { 
    if (running) 
    { 
     running = false; 
     thread.join(); 
    } 
    } 

    std::thread thread; 
    boost::python::list messages; 
    std::atomic<bool> running; 
}; 

BOOST_PYTHON_MODULE(example) 
{ 
    // Force the GIL to be created and initialized. The current caller will 
    // own the GIL. 
    PyEval_InitThreads(); 

    namespace python = boost::python; 
    python::class_<foo, boost::noncopyable>("Foo", python::init<>()) 
    .def("start", &foo::start) 
    .def("stop", &foo::stop) 
    ; 
} 

Интерактивное использование:

>>> import example 
>>> import time 
>>> foo = example.Foo() 
>>> foo.start() 
>>> time.sleep(3) 
0 
1 
2 
>>> foo.stop() 
>>> 
Смежные вопросы