В принципе, кажется, что существует массивная путаница/неопределенность, когда именно PyEval_InitThreads()
должен быть вызван, и какие сопутствующие вызовы API необходимы. официальная документация Python, к сожалению, очень неоднозначна. Уже есть qaru.site/info/308127/... по этой теме, и действительно, я уже лично задал вопрос почти идентичный к этому, поэтому я не буду особенно удивлен, если он будет закрыт как дубликат; но считают, что, по-видимому, нет окончательного ответа на этот вопрос. (К сожалению, у меня нет Guido Van Rossum на скоростном наборе.)
Во-первых, дайте определение области вопроса здесь: что я хочу сделать? Ну... Я хочу написать модуль расширения Python в C, который будет:
- Создавать рабочие потоки с помощью API
pthread
в C - Вызывать обратные вызовы Python из этих потоков C
Хорошо, давайте начнем с самих документов Python. Python 3.2 docs говорят:
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 docs также говорит, что PyEval_ReleaseLock
устарел. Вместо этого мы должны использовать PyEval_SaveThread
, чтобы освободить GIL:
PyThreadState * PyEval_SaveThread()
Отпустите глобальную блокировку интерпретатора (если она была создана и потоком поддержка включена) и reset состояние потока в NULL, возвращая состояние предыдущего потока (которое не равно NULL). Если блокировка была созданный, текущий поток должен был его получить.
Er... ладно, поэтому я предполагаю, что модуль расширения C должен сказать:
PyEval_InitThreads();
PyThreadState* st = PyEval_SaveThread();
В самом деле, это именно то, что говорит qaru.site/info/308127/..... За исключением случаев, когда я на самом деле пытаюсь это на практике, интерпретатор Python немедленно seg-faults, когда я импортирую модуль расширения. Ницца.
Хорошо, теперь я отказываюсь от официальной документации 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()
просто необъяснимо seg-faults.
Хорошо... возможно, по какой-то причине, которая сейчас не в моем понимании, модуль расширения C не должен выпускать GIL. Я пробовал это... и, как и ожидалось, как только другой поток попытается получить GIL (используя PyGILState_Ensure), программа зависает от тупика. Так что да... вам действительно нужно освободить GIL после вызова PyEval_InitThreads()
.
Итак, вопрос следующий: как вы отпустите GIL после вызова PyEval_InitThreads()
И в более общем смысле: что должен сделать модуль C-расширения, чтобы иметь возможность безопасно вызывать код Python из рабочих C-потоков?