PyEval_InitThreads в Python 3: как / когда его вызывать? (сага продолжается до тошноты)
В основном там, кажется,массивный путаница / двусмысленность, когда именноPyEval_InitThreads()
должен быть вызван, и какие сопутствующие вызовы API необходимы.официальная документация Python к сожалению очень неоднозначно. Уже естьмного вопросов по stackoverflow относительно этой темы, и действительно, ялично я ужезадал вопрос практически идентичный к этому, так что я выигралбыть особенно удивленным, если это закрыто как дубликат; но учтите, что, похоже, нет однозначного ответа на этот вопрос. (К сожалению, я неу Гвидо Ван Россума есть быстрый набор.)
Во-первых, давайтеОпределите суть вопроса здесь:что я хочу сделать? Хорошо ... Я хочу написать модуль расширения Python на C, который будет:
Spawn рабочие потоки с использованиемpthread
API в CВызвать обратные вызовы Python из этих потоков CХорошо, так давайтеНачнем с самих документов Python.Python 3.2 документы сказать:
void PyEval_InitThreads ()
Инициализируйте и получите глобальную блокировку интерпретатора. Он должен вызываться в основном потоке перед созданием второго потока или участием в любых других операциях потока, таких как PyEval_ReleaseThread (tstate). Это не нужно перед вызовом PyEval_SaveThread () или PyEval_RestoreThread ().
Итак, мое понимание здесь таково:
Любой модуль расширения C, который порождает потоки, должен вызыватьPyEval_InitThreads()
из основного потока, прежде чем будут созданы другие потокипризваниеPyEval_InitThreads
запирает GILТак что здравый смысл говорит нам, что любой модуль расширения C, который создает потоки, должен вызыватьPyEval_InitThreads()
и затем отпустите глобальную блокировку интерпретатора. Хорошо, кажется достаточно простым. Такprima facie, все это'Требуется следующий код:
PyEval_InitThreads(); /* initialize threading and acquire GIL */
PyEval_ReleaseLock(); /* Release GIL */
Кажется, достаточно просто ... но, к сожалению, документы по Python 3.2также скажи этоPyEval_ReleaseLock
былосуждается, Вместо этого мыдолжен использоватьPyEval_SaveThread
чтобы выпустить GIL:
PyThreadState * PyEval_SaveThread ()
Снимите глобальную блокировку интерпретатора (если она была создана и поддержка потоков включена) и сбросьте состояние потока в NULL, возвращая предыдущее состояние потока (которое не равно NULL). Если блокировка была создана, текущий поток должен ее получить.
Э-э ... хорошо, так что я думаю, что модуль расширения C должен сказать:
PyEval_InitThreads();
PyThreadState* st = PyEval_SaveThread();
Действительно, это именно то, чтоэтот ответ stackoverflow говорит. За исключением случаев, когда я на самом делепытаться на практике интерпретатор Python сразу же вызывает ошибки при импорте модуля расширения.Ницца.
Хорошо, теперь яЯ отказываюсь от официальной документации по Python и обращаюсь к Google. Так,этот случайный блог утверждает, что все, что вам нужно сделать из модуля расширения, это позвонитьPyEval_InitThreads()
, Конечно, документация утверждает, чтоPyEval_InitThreads()
приобретает GIL, и, действительно,быстрая проверка исходного кода дляPyEval_InitThreads()
вceval.c
показывает, что он действительно вызывает внутреннюю функциюtake_gil(PyThreadState_GET());
ТакPyEval_InitThreads()
определенно приобретает GIL. Тогда я подумаю, что вам нужно как-то освободить GIL после вызова.PyEval_InitThreads()
Но как? PyEval_ReleaseLock()
устарела, иPyEval_SaveThread()
просто необъяснимые ошибки.
Хорошо ... так что, может быть, по какой-то причине, которая в настоящее время за пределами моего понимания, модуль расширения Cне нужно выпустить GIL. Я попробовал это ... и, как и ожидалось, как только другой поток попытается получить GIL (используяPyGILState_Ensure), программа зависает из тупика. Так что да ... тыдействительно нужно освободить GIL после звонка.PyEval_InitThreads()
Итак, снова вопрос:как вы отпускаете GIL после звонка?PyEval_InitThreads()
И в целом:Что именно должен делать модуль C-extension, чтобы иметь возможность безопасно вызывать код Python из рабочих C-потоков?