Вот небольшая программа на C++, встраивающая Python.
Он работает с Python 3.11.6, но с Python 3.12.0 происходит сбой:
#include <iostream>
#include "omp.h"
#include "Python.h"
int main()
{
Py_Initialize();
#pragma omp parallel
{
#pragma omp single
{
std::cout << "One character:"<<std::endl;
PyObject *nameobj1 = PyUnicode_FromString("a");
std::cout << nameobj1 << std::endl;
Py_DECREF(nameobj1);
std::cout << "Two characters:"<<std::endl;
PyObject *nameobj2 = PyUnicode_FromString("aa");
std::cout << nameobj2 << std::endl;
Py_DECREF(nameobj2);
}
}
Py_Finalize();
}
Компиляция и запуск с версией 3.11:
$ g++ pytest.cpp `python3.11-config --ldflags --cflags` -lpython3.11 -fopenmp
$ ./a.out
One character:
0x730a12d466e0
Two characters:
0x730a121f33f0
Компиляция и запуск с версией 3.12:
$ g++ pytest.cpp `python3.12-config --ldflags --cflags` -lpython3.12 -fopenmp
$ ./a.out
One character:
0x734752e48a08
Two characters:
Segmentation fault (core dumped)
Изменилось ли что-то в Python 3.12, что не позволяет использовать PyUnicode_FromString более чем с одним символом в openMP? Есть ли обходной путь?
Примечания:
-fopenmpgdb:#0 0x00007ffff77f3f80 in _PyInterpreterState_GET () at ../Include/internal/pycore_pystate.h:118
#1 get_state () at ../Objects/obmalloc.c:866
#2 _PyObject_Malloc (ctx=<optimized out>, nbytes=43) at ../Objects/obmalloc.c:1563
#3 0x00007ffff782b509 in PyUnicode_New (maxchar=<optimized out>, size=2) at ../Objects/unicodeobject.c:1208
#4 PyUnicode_New (size=2, maxchar=<optimized out>) at ../Objects/unicodeobject.c:1154
#5 0x00007ffff7837081 in unicode_decode_utf8 (s=<optimized out>, size=2, error_handler=_Py_ERROR_UNKNOWN, errors=0x0, consumed=0x0)
at ../Objects/unicodeobject.c:4647
#6 0x0000555555555422 in main._omp_fn.0(void) () at pytest.cpp:19
#7 0x00007ffff7f6b48e in gomp_thread_start (xdata=<optimized out>) at ../../../src/libgomp/team.c:129
#8 0x00007ffff6e97b5a in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:444
#9 0x00007ffff6f285fc in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:78
Судя по ответу ниже, такое поведение имеет смысл. Главный поток владеет блокировкой и поэтому может получить доступ к GIL без ошибок. Использование single выбирает случайный поток, который в данный момент может не владеть блокировкой.





В вашем коде есть ошибка, вы никогда не получаете GIL внутри дочерних потоков, вы должны получить GIL при создании или удалении (или изменении) любого объекта Python (за некоторыми исключениями в части изменения), ваш код просто не аварийно завершился в python3.11, но происходит сбой в python3.12
Некоторые состояния интерпретатора являются локальными для потока, и блокировка GIL правильно инициализирует это состояние.
Чтобы получить и удалить GIL, используйте PyGILState_Ensure и PyGILState_Release соответственно.
Вам также необходимо удалить GIL из основного потока перед параллельным разделом, чтобы избежать взаимоблокировок.
Я думаю, что самым большим изменением является Per Interpreter GIL, который был добавлен в python3.12, который переместил больше состояний в раздел threadlocal, что привело к сбою вашего кода. До этого изменения ваш код был неправильным, но он не давал сбоев.
Спасибо за это. Означает ли это, что внутри параллельного раздела невозможно вызывать функции Py? Могу ли я заблокировать GIL перед параллельным разделом, а затем удалить его после параллельного раздела?
@fffred, вы можете выполнять вызовы внутри параллельного раздела, просто тот, кто выполняет этот вызов, должен заблокировать gil перед выполнением вызова и разблокировать его, когда вызов закончится, в основном только один поток может использовать объекты Python одновременно, вы можете создайте несколько интерпретаторов в одном процессе, используя API подинтерпретаторов, но вы не можете передавать объекты Python между этими интерпретаторами.
@fffred, я думаю, что ваш код специально ломается в python3.12 из-за каждого интерпретатора-gil, который был добавлен в python3.12, это привело к самым большим изменениям в локальных данных потока в интерпретаторе.
Еще раз спасибо за эти объяснения. К сожалению, это очень сложно проверить, поскольку это кажется случайным.
Я только что узнал, что замена
#pragma omp singleна#pragma omp masterработает! Я думаю, это обходной путь, но для меня это не имеет смысла.