PyEval_InitThreads w Pythonie 3: Jak to nazwać? (saga trwa nadal na nudności)
Zasadniczo wydaje się, że tak jestmasywny zamieszanie / niejednoznaczność kiedy dokładniePyEval_InitThreads()
ma być wywoływane, a co potrzebne są wywołania API. Theoficjalna dokumentacja Pythona jest niestety bardzo niejednoznaczny. Już jestwiele pytań na temat stackoverflow w tym temacie, a nawet osobiście jużzadałem pytanie prawie identyczne do tego, więc nie będę szczególnie zaskoczony, jeśli to zostanie zamknięte jako duplikat; ale uważam, że nie ma ostatecznej odpowiedzi na to pytanie. (Niestety, nie mam Guido Van Rossuma na szybkim wybieraniu).
Po pierwsze, zdefiniujmy tutaj zakres pytania:co chcę zrobić? Cóż ... Chcę napisać moduł rozszerzenia Pythona w C, który:
Zaimportuj wątki robocze za pomocąpthread
API w CWywołaj wywołania zwrotne Pythona z tych wątków C.Dobra, zacznijmy od samych dokumentów Pythona. TheDokumenty Pythona 3.2 mówić:
void PyEval_InitThreads ()
Zainicjuj i uzyskaj globalną blokadę interpretera. Powinien być wywołany w głównym wątku przed utworzeniem drugiego wątku lub zaangażowaniem się w inne operacje wątku, takie jak PyEval_ReleaseThread (tstate). Nie jest potrzebny przed wywołaniem PyEval_SaveThread () lub PyEval_RestoreThread ().
Więc moje zrozumienie jest następujące:
Każdy moduł rozszerzenia C, który tworzy wątki, musi wywoływaćPyEval_InitThreads()
z głównego wątku przed pojawieniem się innych wątkówPowołaniePyEval_InitThreads
blokuje GILTak więc zdrowy rozsądek powie nam, że każdy moduł rozszerzenia C, który tworzy wątki, musi zadzwonićPyEval_InitThreads()
, a następnie zwolnij blokadę globalnego tłumacza. Ok, wydaje się dość prosty. Więcprima faciewszystko, czego potrzeba, to następujący kod:
PyEval_InitThreads(); /* initialize threading and acquire GIL */
PyEval_ReleaseLock(); /* Release GIL */
Wydaje się dość prosty ... ale niestety, Python 3.2 docsrównież Powiedz toPyEval_ReleaseLock
byłprzestarzałe. Zamiast tego powinniśmy użyćPyEval_SaveThread
w celu wydania GIL:
PyThreadState * PyEval_SaveThread ()
Zwolnij blokadę globalnego interpretera (jeśli została utworzona i obsługa wątków jest włączona) i zresetuj stan wątku do NULL, zwracając poprzedni stan wątku (który nie jest NULL). Jeśli blokada została utworzona, bieżący wątek musi ją zdobyć.
Eee ... ok, więc chyba moduł rozszerzenia C musi powiedzieć:
PyEval_InitThreads();
PyThreadState* st = PyEval_SaveThread();
Rzeczywiście, to jest dokładnie to, cota odpowiedź typu stackoverflow mówi. Z wyjątkiem sytuacji, kiedy faktyczniepróbować w praktyce interpreter Pythona natychmiast segreguje błędy, gdy importuję moduł rozszerzenia. Ładny.
Dobra, więc teraz rezygnuję z oficjalnej dokumentacji Pythona i zwracam się do Google. Więc,ten losowy blog twierdzi, że wszystko, co musisz zrobić z modułu rozszerzenia, to zadzwonićPyEval_InitThreads()
. Oczywiście tak twierdzi dokumentacjaPyEval_InitThreads()
nabywa GIL i rzeczywiście, aszybka kontrola kodu źródłowego dlaPyEval_InitThreads()
wceval.c
ujawnia, że rzeczywiście wywołuje funkcję wewnętrznątake_gil(PyThreadState_GET());
WięcPyEval_InitThreads()
Zdecydowanie nabywa GIL. Pomyślałbym wtedy, że absolutnie musisz jakoś uwolnić GIL po wywołaniuPyEval_InitThreads()
. Ale jak? PyEval_ReleaseLock()
jest przestarzałe iPyEval_SaveThread()
po prostu niewytłumaczalnie seg.
Dobra ... więc może z jakiegoś powodu, który jest obecnie poza moim zrozumieniem, moduł rozszerzenia Cnie trzeba zwolnić GIL. Próbowałem tego ... i, zgodnie z oczekiwaniami, jak tylko inny wątek spróbuje zdobyć GIL (używającPyGILState_Ensure), program zawiesza się z powodu zakleszczenia. Więc tak ... tynaprawdę tak trzeba zwolnić GIL po wywołaniuPyEval_InitThreads()
.
Więc znowu pytanie brzmi:jak zwolnisz GIL po wywołaniuPyEval_InitThreads()
?
I bardziej ogólnie:co dokładnie musi zrobić moduł rozszerzenia C, aby móc bezpiecznie wywołać kod Pythona z wątków roboczych C?