Python, Threads, GIL и C ++ - PullRequest
       17

Python, Threads, GIL и C ++

1 голос
/ 20 декабря 2009

Есть ли способ заставить boost :: python контролировать Python GIL для каждого взаимодействия с python?

Я пишу проект с boost :: python. Я пытаюсь написать оболочку C ++ для внешней библиотеки и управлять библиотекой C ++ с помощью сценариев Python. Я не могу изменить внешнюю библиотеку, только мою программу-обертку. (Я пишу приложение функционального тестирования для указанной внешней библиотеки)

Внешняя библиотека написана на C и использует указатели функций и обратные вызовы для выполнения большой работы. Это система обмена сообщениями, поэтому, когда приходит сообщение, вызывается функция обратного вызова, например.

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

Внешняя библиотека создает потоки для обработки сообщений, отправки сообщений, обработки и т. Д. Некоторые из этих обратных вызовов могут вызываться из разных процессов, и недавно я обнаружил, что python не является потокобезопасным.

Эти наблюдатели могут быть определены в Python, поэтому я должен иметь возможность вызывать Python, а Python должен вызывать мою программу в любой момент.

Я настраиваю объект и наблюдателя примерно так

class TestObserver( MyLib.ConnectionObserver ):
    def receivedMsg( self, msg ):
        print("Received a message!")

ob = TestObserver()
cnx = MyLib.Conection()
cnx.attachObserver( ob )

Затем я создаю источник для отправки в соединение и вызывается функция receiveMsg.

Итак, обычный source.send ('msg') войдет в мое приложение C ++, перейдет в библиотеку C, которая отправит сообщение, получит соединение, затем вызовет функцию обратного вызова, которая возвращается в мой C ++ библиотека и соединение пытается уведомить всех наблюдателей, которые на данный момент являются классом python, поэтому он вызывает этот метод.

И, конечно, обратный вызов вызывается из потока соединения, а не из основного потока приложения.

Вчера все рушилось, я не смог отправить 1 сообщение. Затем, покопавшись в архивах Cplusplus-sig, я узнал о GIL и нескольких изящных функциях для блокировки вещей.

Так что моя оболочка C ++ для моего класса-наблюдателя теперь выглядит так

struct IConnectionObserver_wrapper : Observers::IConnectionObserver, wrapper<Observers::IConnectionObserver>
{
    void receivedMsg( const Message* msg )
    {
        PyGILState_STATE gstate = PyGILState_Ensure();
        if( override receivedMsg_func = this->get_override( "receivedMsg" ) )
            receivedMsg_func( msg );
        Observers::IConnectionObserver::receivedMsg( msg );
        PyGILState_Release( gstate );
    }
}

И это РАБОТАЕТ, однако, когда я пытаюсь отправить более 250 сообщений примерно так

for i in range(250)
    source.send('msg")

снова падает. С тем же сообщением и симптомами, что и раньше,

PyThreadState_Get: no current thread

поэтому я думаю, что в этот раз у меня возникла проблема с вызовом моего приложения на C ++, а не с вызовом на python.

У меня вопрос, есть ли способ заставить boost :: python обрабатывать сам GIL для каждого взаимодействия с python? Я не могу найти ничего в коде, и это действительно трудно, пытаясь найти, где вызов source.send входит в boost_python: (

Ответы [ 3 ]

4 голосов
/ 22 января 2010

Не знаю точно о вашей проблеме, но взгляните на CallPolicies:

http://www.boost.org/doc/libs/1_37_0/libs/python/doc/v2/CallPolicies.html#CallPolicies-concept

Вы можете определить новые политики вызовов (например, одна политика вызовов "return_internal_reference"), которые будут выполнять некоторый код до и / или после выполнения функции C ++ с переносом. Я успешно реализовал политику вызовов для автоматического освобождения GIL перед выполнением функции в C ++ и получением ее снова перед возвратом в Python, поэтому я могу написать такой код:

.def( "long_operation", &long_operation, release_gil<>() );

Политика вызовов может помочь вам в написании этого кода.

4 голосов
/ 20 декабря 2009

Я нашел очень малоизвестное сообщение в списке рассылки, в котором говорилось, что нужно использовать PyEval_InitThreads (); в BOOST_PYTHON_MODULE и это действительно, казалось, остановило сбои.

Это все еще дерьмовый выстрел, независимо от того, сообщает ли программа все полученные сообщения или нет. Если я отправляю 2000, большую часть времени он говорит, что получил 2000, но иногда он сообщает значительно меньше.

Я подозреваю, что это может быть связано с тем, что потоки одновременно обращаются к моему счетчику, поэтому я отвечаю на этот вопрос, потому что это другая проблема.

Чтобы исправить, просто сделайте

BOOST_PYTHON_MODULE(MyLib)
{
    PyEval_InitThreads();
    class_ stuff
1 голос
/ 13 августа 2010

Я думаю, что лучший подход состоит в том, чтобы избежать GIL и обеспечить, чтобы ваше взаимодействие с python было однопоточным.

В данный момент я разрабатываю инструмент тестирования на основе boost.python и думаю, что, вероятно, я буду использовать очередь производителей / потребителей для отправки событий из многопоточных библиотек, которые будут последовательно считываться потоком python.

...