Поведение указателя во "многопоточном" встроенном питоне
Это может быть немного трудно ответить, потому что это полу-специфично для нашей архитектуры, но мое копание заставляет меня полагать, что основная проблема носит общий характер.
У меня есть несколько библиотек DLL, которые встроили Python с использованием Boost.Python. Эти DLL загружаются в программу Windows, которая запускает каждую DLL в своем собственном потоке. Когда программа запускает библиотеку DLL, она запускает сценарий, а затем сценарий запускается в цикле, пока ему не будет приказано остановиться. Когда ему говорят об остановке, основной поток зависает в памяти (в ожидании команды "Перезапустить"), но рабочий поток, выполняющий сценарий, корректно завершается и удаляется из памяти. Он многопоточный, так как каждый скрипт технически выполняется в своем собственном потоке, но все они полностью независимы друг от друга и не взаимодействуют.
(Связанное ограничение: всеобъемлющая программа понятия не имеет, что эти модули запускают Python, и модули не могут взаимодействовать друг с другом, и программа не может координировать их взаимодействия Python. Забавно.)
У меня были проблемы с GIL, и все эти потоки втискивались в интерпретатор без сбоев, но я сработал. Получение GIL перед входом в поток заставило всех играть хорошо. Вот код, который запускает скрипт:
BOOST_PYTHON_MODULE(SimplePythonModule)
{
class_<OurModuleClass, boost::noncopyable, bases<OurBaseModuleClasses>>("OurModuleClass", no_init)
.def("GetMyName", &OurModuleClass::GetMyName);
def("LogSomething", &LogSomething); // A program-wide API function
}
int PythonModule::RunScript(OurModuleClass * m_Ptr)
{
int retval = 0;
PyGILState_STATE gstate;
PyThreadState * state;
Py_Initialize(); // Py3.7 auto-inits threads here
gstate = PyGILState_Ensure(); // Acquire the lock
state = Py_NewInterpreter();
object main_module = object(handle<(borrowed(PyImport_AddModule("__main__"))));
dict global = extract<dict>(main_module.attr("__dict__"));
object result = exec_file((LPCTSTR)m_Ptr->m_sScriptName, global, global);
object runner = global["EntryFunction"];
retval = extract<int>(runner(boost::python::ptr(m_Ptr))); // Passing the pointer to the script
PyEval_ReleaseThread(state);
return retval;
}
Внутри скрипта они выполняют такие вещи, как вызов функции LogSomething всеобъемлющей программы, когда они делают свое дело:
from SimplePythonModule import *
def EntryFunction(ptr):
while True:
# ... things happen here ...
LogSomething(ptr.GetMyName())
Это прекрасно работает, они правильно печатают свое имя при каждом проходе, и наш журнал выглядит следующим образом (при условии, что переменная имени нашего модуля совпадает с именем его модуля python):
SimplePythonModule: SimplePythonModule
Но затем все становится странным, когда я компилирую копию этого модуля, изменяя имя, объявленное в BOOST_PYTHON_MODULE (скажем, компиляция с увеличивающимися именами 1-4), и компилирую как отдельные библиотеки DLL с одинаковыми именами. В циклах сначала все будет выглядеть хорошо, каждый печатает свое имя правильно:
SimplePythonModule1: SimplePythonModule1
SimplePythonModule2: SimplePythonModule2
SimplePythonModule3: SimplePythonModule3
Но затем, если я остановлю первый модуль и перезапущу его (т. Е. Основной поток модуля все еще находится в памяти, но его рабочий поток, выполняющий сценарии, был уничтожен), это произойдет:
SimplePythonModule1: SimplePythonModule4
Программа, которая печатает журналы, знает, что это Module1, который вызвал функцию, но указатель в скрипте считает, что он принадлежит к последнему загруженному модулю. Однако он выполняется в файле сценария, назначенном для Module1. Я определил, что это происходит только внутри Python: указатель, данный функции RunScript, знает себя как до, так и после вызова Python, поэтому все, что происходит, находится внутри интерпретатора.
Интересно, что эта проблема исчезнет, если я переименую файлы классов перед компиляцией, а не просто имя, предоставленное BOOST_PYTHON_MODULE(SimplePythonModule). Поэтому, если OurModuleClass переименовывается в OurModuleClass2 и перекомпилируется, указатели внутри функции никогда не запутаются в их происхождении.
Что именно здесь происходит, и есть ли способ исправить это, не требуя 408 изменений имени класса Find and Replace для каждого независимого модуля Python, который я хочу скомпилировать?