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 GIL

Tak 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?

questionAnswers(7)

yourAnswerToTheQuestion