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!
Author: MariusSiuram, 2014-09-26

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).

 35
Author: sterin,
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