Python multi-thread multi-interpreter C API
Bawię się w C API dla Pythona, ale dość trudno jest zrozumieć niektóre narożne przypadki. Mógłbym go przetestować, ale wydaje się, że jest podatny na błędy i czasochłonny. Więc przyszedłem zobaczyć, czy ktoś już to zrobił.
Pytanie brzmi, jaki jest prawidłowy sposób zarządzania wielowątkowością z sub-interpreterami, bez bezpośredniego związku między wątkami a sub-interpreterami?
Py_Initialize();
PyEval_InitThreads(); /* <-- needed? */
_main = PyEval_SaveThread(); /* <-- acquire lock? does it matter? */
/* maybe do I not need it? */
i1 = Py_NewInterpreter();
i2 = Py_NewInterpreter();
Czy używam mutex ' a? Czy wymagane jest używanie zamków? Funkcja gwintowana powinno być coś w stylu: (wątki nie są Pythonem, prawdopodobnie wątki POSIX)
Thread1
_save = PyThreadState_Swap(i1);
// python work
PyThreadState_Restore(_save);
Thread2 (prawie identyczny)
_save = PyThreadState_Swap(i1);
// python work
PyThreadState_Restore(_save);
Thread3 (prawie identyczny, ale z sub-interpreterem i2
)
_save = PyThreadState_Swap(i2);
// python work
PyThreadState_Restore(_save);
Czy to prawda? Czy jest to ogólny przypadek, dla którego chcę osiągnąć? Czy są warunki wyścigowe?
Dzięki! 1 answers
Interpretery podrzędne w Pythonie nie są dobrze udokumentowane ani nawet dobrze obsługiwane. Poniżej przedstawiam najlepsze z moich niewymagających Wydaje się, że działa dobrze w praktyce.
Three są dwoma ważnymi pojęciami, które należy zrozumieć, gdy mamy do czynienia z wątkami i sub interpreterami w Pythonie. Po pierwsze, interpreter Pythona nie jest tak naprawdę wielowątkowy. Posiada globalną blokadę interpretera (Gil), którą należy nabyć, aby wykonać prawie każdą operację Pythona (istnieje kilka rzadkich wyjątków od tej reguła).
Po drugie, każda kombinacja interpretera wątku i sub musi mieć swój własny stan wątku. Interpreter tworzy stan wątku dla każdego wątku zarządzanego przez niego, ale jeśli chcesz używać Pythona z wątku nie utworzonego przez tego interpretera, musisz utworzyć nowy stan wątku.
Najpierw musisz utworzyć interpretery podrzędne:
Initialize Python
Py_Initialize();
Initialize Python thread support
Wymagane, jeśli plan wywołania Pythona z wielu wątków). Ten telefon nabywa również GIL.
PyEval_InitThreads();
Zapisz aktualny stan wątku
Mogłem użyć PyEval_SaveThread()
, ale jednym z efektów ubocznych jest uwolnienie GIL, które następnie musi zostać ponownie pozyskane.
PyThreadState* _main = PyThreadState_Get();
Utwórz podstronę
PyThreadState* ts1 = Py_NewInterpreter();
PyThreadState* ts2 = Py_NewInterpreter();
Przywracanie głównego stanu wątku interpretera
PyThreadState_Swap(_main);
Mamy teraz dwa stany wątków dla sub interpreterów. Te stany wątku to ważne tylko w wątku, w którym zostały utworzone. Każdy wątek, który chce użyć jednego z podrzędnych interpreterów, musi utworzyć stan wątku dla tej kombinacji wątku i interpretera.
Używanie interpretera sub z nowego wątku
Oto przykładowy kod do użycia interpretera podrzędnego w nowym wątku, który nie jest tworzony przez interpretera podrzędnego. Nowy wątek musi zdobyć GIL, utworzyć nowy stan wątku dla kombinacji wątku i interpretera i uczynić go aktualny stan wątku. Na koniec należy zrobić odwrotnie, aby oczyścić.
void do_stuff_in_thread(PyInterpreterState* interp)
{
// acquire the GIL
PyEval_AcquireLock();
// create a new thread state for the the sub interpreter interp
PyThreadState* ts = PyThreadState_New(interp);
// make ts the current thread state
PyThreadState_Swap(ts);
// at this point:
// 1. You have the GIL
// 2. You have the right thread state - a new thread state (this thread was not created by python) in the context of interp
// PYTHON WORK HERE
// release ts
PyThreadState_Swap(NULL);
// clear and delete ts
PyThreadState_Clear(ts);
PyThreadState_Delete(ts);
// release the GIL
PyEval_ReleaseLock();
}
Korzystanie z interpretera sub z nowego wątku (post Python 3.3)
Poprzednia do_stuff_in_thread()
nadal działa ze wszystkimi aktualnymi wersjami Pythona. Jednak Python 3.3 jest przestarzały PyEval_AcquireLock()
/PyEval_ReleaseLock()
, co doprowadziło do pewnej zagadki.
Jedynym udokumentowanym sposobem uwolnienia GIL jest wywołanie PyEval_ReleaseThread()
lub PyEval_SaveThread()
, które wymagają stanu wątku, podczas czyszczenia i usuwania bieżącego stan wątku wymaga trzymania GIL. Oznacza to, że można albo zwolnić GIL, albo oczyścić stan wątku, ale nie oba.
Na szczęście istnieje rozwiązanie - PyThreadState_DeleteCurrent()
usuwa bieżący stan wątku, a następnie zwalnia GIL. [To API zostało udokumentowane dopiero od wersji 3.9, ale istniało przynajmniej od wersji 2.7 Pythona]
Ten zmodyfikowany do_stuff_in_thread()
działa również ze wszystkimi aktualnymi wersjami Pythona.
void do_stuff_in_thread(PyInterpreterState* interp)
{
// create a new thread state for the the sub interpreter interp
PyThreadState* ts = PyThreadState_New(interp);
// make it the current thread state and acquire the GIL
PyEval_RestoreThread(ts);
// at this point:
// 1. You have the GIL
// 2. You have the right thread state - a new thread state (this thread was not created by python) in the context of interp
// PYTHON WORK HERE
// clear ts
PyThreadState_Clear(ts);
// delete the current thread state and release the GIL
PyThreadState_DeleteCurrent();
}
Teraz każdy wątek może zrobić "po": {]}
Thread1
do_stuff_in_thread(ts1->interp);
Thread2
do_stuff_in_thread(ts1->interp);
Thread3
do_stuff_in_thread(ts2->interp);
Wywołanie Py_Finalize()
niszczy wszystkie interpretery podrzędne. Alternatywnie można je zniszczyć ręcznie. Należy to zrobić w głównym wątku, używając Stanów wątku utworzonych podczas tworzenia sub-interpreterów. Na końcu Zmień stan głównego wątku interpretera na bieżący.
// make ts1 the current thread state
PyThreadState_Swap(ts1);
// destroy the interpreter
Py_EndInterpreter(ts1);
// make ts2 the current thread state
PyThreadState_Swap(ts2);
// destroy the interpreter
Py_EndInterpreter(ts2);
// restore the main interpreter thread state
PyThreadState_Swap(_main);
Mam nadzieję, że wszystko będzie jaśniejsze.
Mam mały kompletny przykład napisany w C++ na github i innym również na github (wariant post Pythona 3.3).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-03-05 08:54:38