All streams
Search
Write a publication
Pull to refresh
49
0
Владимир Керимов @Qualab

User

Send message
Boost.Python замечательная библиотека. Единственное ограничение: постарайтесь обойтись без глобальный и статических переменных типа boost::python::object и его наследников. Иначе после Py_Finalize в деструкторе ~object() вы поимеете кучу неприятных последствий. В целом Boost.Python показал себя замечательной библиотекой, по сути ничего лишнего с одной стороны, даже mpl там очень в тему, через fusion красиво передаются аргументы и всё это очень здорово упрощает конечный код.
По сути на стороне C++ всё сводится к тройке: import(), attr(), extract().
Единственное где может ещё потребоваться Си-API — при извлечения информации об исключении из Python, когда поймаете catch( boost::python::error_already_set& ) { }, но это пожалуй единственное явное белое пятно в Boost.Python при работе с Python из C++.
Не всё можно понять глядя на статичный код. Я бы крайне рекомендовал вам заглянуть в пошаговую отладку в данном приложении. Вам, как человеку любознательному, было бы крайне полезно узнать, что после выполнения макроса Py_BEGIN_ALLOW_THREADS (аналог выполняется в конце конструктора PyMainThread) значение возвращаемое запросом на GIL через PyGILState_Ensure будет PyGILState_UNLOCKED.
Это фактически означает, что для каждого потока в отдельности разрешается хапнуть свой PyGILState_STATE одновременно.
Представляете какая красота, одни и те же функции и два поведения. Фактически ваш метод анализа кода даёт сбой именно в этот момент.
Эта маленькая тонкость даёт возможность запустить одновременно N под-интерпретаторов со своим PyThreadState, для которого в каждом потоке делается фактически свой запрос на GIL.
Я бы согласился, если бы не одно но. Что по-вашему делает эта функция: PyOS_AfterFork
Боюсь для C++ приложения использующего Python в общем случае можно упустить такую мелочь как
«to update some internal state after a process fork; this should be called in the new process if the Python interpreter will continue to be used. If a new executable is loaded into the new process, this function does not need to be called»
Понятно что для multiprocessing это не актуально, а вот для fork боюсь тут надо разобраться, что это за «some internal state after a process fork».
Не хотелось бы наступить на невидимые грабли с внутренней кухней данных в Python.
Хм, попробуйте обогнать моё приложение своим скриптом, при условии что они оба будут делать одинаковую работу.
Разумеется оба должны быть многопоточными и оба должны параллелить некоторый ряд действий.
Уверен, что ни при каких прочих равных модуль threading не сможет сравниться с нативными потоками, где делается аналогичная работа в уже подготовленных для работы интерпретаторах параллельно без общего GIL.
Надо же, я даже испугался, если бы не одно но:
> в каждом потоке используется PyGILState_Ensure в начале выполнения и отпускается уже только в самом конце, если бы GIL был глобален, ничего бы не получилось, все бы зависли.
На самом деле ключевое значение здесь PyGILState_STATE приводящее к пониманию когда берётся состояние глобальное, а когда нет. Значение UNLOCKED приходящее в порождённые потоки позволяет зажать GIL по-потоково для под-интерпретаторов.
По роду деятельности я должен поддерживать работоспособность на Windows. Там всё довольно печально. Поэтому же решение с fork меня не устраивало. Сейчас я уже этому даже рад, моё решение кроссплатформенное, использует один процесс и изолирует память интерпретатора (под-интерпретаторов) между потоками. Почти идеал. Осталось только найти косяки.
Копирование пустого интерпретатора функцией Py_NewInterpreter у меня заняло около 200 мс в среднем, один раз на старте потока. Что разумеется мелочи для инициализации приложения, в сравнении с тем, что для Python версии ниже 3.2 мы получим блокировку вплоть до завершения работы потока, что для длительных операций приводит параллельное исполнение к линейному. В Python 3.2+ разница уже не так заметна, благодаря новой схеме GIL, однако всё равно порядка 2 сек разницы в пользу под-интерпретаторов при условиях: 4 потока, 10 повторов, 0,5 сек. паузы.
Тем, что процесс один, и из приложения на C++ не создаётся ни одного процесса Python. По сути получаете приложение, свободно использующее по интерпретатору в каждом потоке, однако при этом кроме Вашего процесса ни одного не будет создано. Легко использовать общие данные, согласовать между собой задания потоков и завершать их куда проще чем процессы.
Пробовали на Python 3.2+?
Секунду, если он внутри и переключится, то для PyThreadState уже нового под-интерпретатора, и вернётся на выполнение к нему же. Кто сказал что переключения не было? С Python 3.2 переключения идут уже по времени, во время sleep могло быть переключение.
Действительно ли было падение, если да, то покажите код, на котором схема приведённая выше не работает.
Ну здесь на самом деле если не учитывать слой C++ интерпретаторы между собой никак взаимосвязаны, так что решение аналогичное. Но то что на уровне C++ можно устроить диспетчер обмена данными между потоками, конечно плюс.
Межпроцессное взаимодействие на порядок сложнее чем внутрипроцессное между потоками. Проще запустить, но сложнее согласовать и завершить.
Почему не даёт? Та же разделяемая память вне питона вполне видна. Многопоточность даёт честное параллельное выполнение. То что под-интерпретаторы Python изолированы друг от друга — скорее плюс чем минус. Учитывая что физически это один процесс можно сделать модуль на C++ который даёт потокобезопасный доступ к общим переменным.
Собственно смысл в параллельном выполнении и есть. Все остальные плюшки единого процесса остаются.
Потоки созданные в самом питоне через модуль threading используют один общий GIL, в один момент времени выполняется строго один из них. С удовольствием могу померять насколько это медленнее параллельного выполнения, если действительно сомневаетесь.
Важно! В C++ приложении не будет запущен никакой отдельный процесс python.exe, точно так же никакой запуск под-интерпретатора в потоках не породит никаких процессов питона!
Можно смело использовать Python-модули из C++ кода в полной уверенности, что это будет один-единственный процесс.
Под-интерпретаторы изолированы почти полностью, единственное что взаимодействие с ОС будет разумеется из одного процесса, текущего, это следует учитывать.
Если подразумевается 50 интерпретаторов для 50 потоков, то это будет конечно медленнее, поскольку 50-ядерных процессоров пока в продаже не так много. Тем не менее заменив сейчас в тестовом проекте THREAD_NUM на 50, всё благополучно завершилось, выполнялось всё параллельно.
Единственное что каждый import в каждом отдельном под-интерпретаторе будет выполнен заново, поэтому нужно стараться не импортировать в каждом потоке по гигабайту или то что импортируется по несколько минут.
Стандартные функции работают с UTF-8 в виде std::string или char* просто замечательно, за исключением operator[], поскольку он ссылается именно на элемент данных, а не на символ. Если Вы скажете, что wchar_t и std::wstring лучший выбор, я пожалуй не соглашусь, потому как с UTF-16 также operator[] не обязательно вернёт ссылку на символ. Если в тексте UTF-16 встречается суррогатная пара, то об индексации по символам можно забыть.
Если говорить только об UCS-2, то это настолько малая часть Юникода, по сути вы отказываетесь от 0x10FF0000 символов, то есть около 285 миллионов, в пользу 65,5 тысяч. В UCS-2 не входят даже все основные алфавиты, но кириллица там конечно же есть.
Можете обманывать себя, но по сути использование UCS-2 для кириллицы не сильно отличается от использования кодовой страницы. Проблем с другой кодовой страницей Вы конечно не получите, но интернационализации кодировки также не будет. Первый же выход крупного проекта ударит по вам незнакомыми доселе проблемами с хинди и, возможно, каким-нибудь кантонским, для избежания которых и существует общепринятый Юникод.
Используйте пожалуйста UTF-16, но также помните, что operator[] от i-го элемента не обязательно ссылается на i-й символ.
Эм, это как мне не работать с кодированным текстом в C++? Стандартная строка на языке C++ подразумевает char*=byte*, то есть базовый как бы намекает один символ=один байт, что ещё на заре C++ было далеко не всегда так. Уже используя UTF-8 мы получаем для Windows массу символов в 2 и 3 байта.
Я понимаю, что есть UCS-2 (обрезок UTF-16) которым Microsoft заткнула большинство дыр с Юникодом. Но подавляющее большинство библиотек на С++ работают с char* и std::string, причём не только сторонних, но и стандартных. Большинство функций уже дублировано через wchar_t* и std::wstring, но не все даже стандартные, даже в WinAPI.
Ну и в конце концов вы правда хотите работать на C++ с wide-strings?
Кроме того, с «широкими строками» в Windows мы получаем три основные проблемы.
1. Нет поддержки суррогатных пар из UTF-16, это дословно означает, что Юникода в Windows нет. Вы рано или поздно огребёте от наших китайских или индийских друзей. То что до сих пор не огребли, значит что либо применение вашего кода локализовано, либо эти ребята тоже пользуются Windows и их алфавиты сильно порезаны применением «основного».
2. Ваше API будет подразумевать постоянный переход от char* к wchar_t*. Как ни крути а большинство использует однобайтовые строки, нужно поддержать сигнатуру стандартных строк на C++.
3. Ваши строки в виде Wide-string сильно распухнут по сравнению с UTF-8 (наиболее компактным представлением Юникода), просто потому что ваш код изобилует пунктуацией, служебными символами и латиницей примерно наполовину минимум. Это очень сильно скажется на траффике и объемах данных на жёстком диске.
Исходя из вышесказанного проще использовать однобайтовую кодировку UTF-8.
Увы, это не так просто, как например в Eclipse CDT или CodeBlocks, там просто указываешь UTF-8 в workspace. Мало того, на логику того что в коде именно строки файловой системы по умолчанию уже может быть заложено много кода. В частности во взаимодействии с БД или веб-клиентом. Если просто перекодировать код большого проекта в UTF-8 и закоммитить, то:
1) получишь тонну проблем в массе стыковых мест, которые не так легко отловить, т.к. код изначально подразумевал переход от локализованной части к интернациональной;
2) к тебе придут все разработчики на MS Visual C++, если ты работаешь в команде больше одного человека, и спросят, с какого у них всё теперь так отображается в виде кракозябр и не работает.

Максимально безболезненно перенести все стыковые проблемы с перекодировкой на Boost.Python.
Если код на C++ пишется с нуля, разумеется лучше будет писать его сразу весь в UTF-8 (без BOM).
Но! Транспорт уменьшится по трафику вдвое, если перекодировать строку в cp1251 перед отправкой. Вспомните на досуге ваши проблемы с укороченными СМС кириллицей. Это всё благодаря транспортному уровню в UTF-8, кириллически символ идёт за два байта и не помещается в один стандартный пакет отправки.
Если использовать boost::python, то сразу получите готовый модуль Python.
Всё что останется — сделать import.
Тогда и потребуется переименование Печки в Менеджера, раньше не надо.

Information

Rating
Does not participate
Location
Ярославль, Ярославская обл., Россия
Date of birth
Registered
Activity

Specialization

Game Developer
Lead