2014-09-26 13 views
12

Używam C API dla Pythona, ale dość trudno jest zrozumieć niektóre przypadki narożne. Mogłem to przetestować, ale wydaje się być podatne na błędy i czasochłonne. Przyjeżdżam tutaj, aby sprawdzić, czy ktoś już to zrobił.Wielowątkowy interpreter języka Python C API

Pytanie brzmi, jaki jest właściwy sposób zarządzania wielowątkowym z subinterpretatorami, bez bezpośredniego związku między wątkami a subinterpretatorami?

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 muteksu? Czy wymagane jest stosowanie zamków? Funkcja gwintowane powinny być coś jak następuje: (Nitki są non-python, prawdopodobnie POSIX wątków)

thread1

_save = PyThreadState_Swap(i1); 
    // python work 
PyThreadState_Restore(_save); 

thread2 (prawie identyczne)

_save = PyThreadState_Swap(i1); 
    // python work 
PyThreadState_Restore(_save); 

Thread3 (prawie identyczny, ale z podpisem interpretacyjnym i2)

_save = PyThreadState_Swap(i2); 
    // python work 
PyThreadState_Restore(_save); 

Czy to prawda? Czy to ogólny przypadek, który chcę osiągnąć? Czy są warunki wyścigu?

Dzięki!

Odpowiedz

14

Podsłuchy w języku Python nie są dobrze udokumentowane, a nawet dobrze obsługiwane. Poniższe wskazówki są najlepsze z moich niedoskonałości. Wydaje się, że działa dobrze w praktyce.

Threre to dwa ważne pojęcia, które należy zrozumieć, gdy zajmujemy się wątkami i podinterpretatorami w Pythonie. Po pierwsze interpreter Pythona nie jest tak naprawdę wielowątkowy. Ma globalny blok interpretera (GIL), który musi zostać zdobyty, aby wykonać prawie każdą operację w Pythonie (istnieje kilka rzadkich wyjątków od tej reguły).

Po drugie, każda kombinacja wątku i podinterpretera musi mieć własny stan wątku. Interpreter tworzy stan wątku dla każdego wątku zarządzanego przez niego, ale jeśli chcesz użyć Pythona z wątku nie utworzonego przez tego interpretera, musisz utworzyć nowy stan wątku.

Najpierw trzeba stworzyć sub tłumaczy:

zainicjować Python

Py_Initialize(); 

Initialize Pythona wsparcia wątek

Wymagane jeśli planują zadzwonić Python z wielu wątków). Wezwanie to również przejmuje GIL.

PyEval_InitThreads(); 

zapisać aktualny stan gwintu

mogę być używane PyEval_SaveThread(), ale jego skutków ubocznych odłączający Gil, która następnie musi być wykupione.

PyThreadState* _main = PyThreadState_Get(); 

Tworzenie sub tłumaczy

PyThreadState* ts1 = Py_NewInterpreter(); 
PyThreadState* ts2 = Py_NewInterpreter(); 

przywracania stanu główny wątek interpreter

PyThreadState_Swap(_main); 

Mamy teraz dwa stany wątku dla sub tłumaczy. Te stany gwintów są ważne tylko w wątku, w którym zostały utworzone. Każdy wątek, który chce użyć jednego z podinterpretatorów, musi utworzyć stan wątku dla tej kombinacji wątku i interpretera.

Używając podmenu interpreter z nowej nici

Oto przykład kodu za pomocą sub interpreter w nowej nici, który nie jest utworzony przez interpreter pomocniczym. Nowy wątek musi uzyskać GIL, utworzyć nowy stan wątku dla kombinacji wątku i interpretere i uczynić go bieżącym stanem wątku. Na koniec należy zrobić odwrotny proces oczyszczania.

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(ts1->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(); 
} 

Teraz każdy wątek może wykonać następujące czynności:

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 wszystkich subintererów. Alternatywnie można go zniszczyć ręcznie. Trzeba to zrobić w głównym wątku, używając stanów wątków utworzonych podczas tworzenia podprocesorów. Na koniec upewnij się, że główny wątek interpretera określa aktualny stan.

// 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 to trochę wyjaśni.

Mam mały kompletny przykład napisany w C++ na github.

+0

Dzięki! Właśnie tego szukałem, "szczegółowego szybkiego startu" - bardziej szczegółowego niż szybkiego :). Gdyby nie ty, zdecydowanie przegapiłbym "stan nici dla tej kombinacji nici i tłumacza". – MariusSiuram

+2

Podsumowując: NIE MOŻESZ korzystać z wielu interpreterów Pythona, które są naprawdę równoległe (wątki sprzętowe na wielu rdzeniach)? – japedo

+1

Prawidłowo. Python używa globalnej blokady interpretera, która pozwala tylko pojedynczemu wątkowi na uruchamianie aktualnego kodu Pythona na raz. Jednak kod C, który wykonuje długą operację, często zwalnia blokadę, dopóki nie wróci do Pythona, aby można było wykonać inny wątek. Oznacza to, że rzeczywiste wykorzystanie będzie zależało od kodu. – sterin

Powiązane problemy