Szukaj…


Uwagi

Programiści Python upewnili się, że interfejs API między threading i multiprocessing jest podobny, aby programiści mogli łatwiej przełączać się między tymi dwoma wariantami.

Moduł wątków

from __future__ import print_function
import threading
def counter(count):
    while count > 0:
        print("Count value", count)
        count -= 1
    return

t1 = threading.Thread(target=countdown,args=(10,))
t1.start()
t2 = threading.Thread(target=countdown,args=(20,))
t2.start()

W niektórych implementacjach Pythona, takich jak CPython, prawdziwej równoległości nie osiąga się za pomocą wątków, ponieważ używa się tak zwanego GIL lub G lobal I nterpreter L ock.

Oto doskonały przegląd współbieżności Pythona:

Współbieżność Pythona autorstwa Davida Beazleya (YouTube)

Moduł wieloprocesowy

from __future__ import print_function
import multiprocessing


def countdown(count):
    while count > 0:
        print("Count value", count)
        count -= 1
    return

if __name__ == "__main__":
    p1 = multiprocessing.Process(target=countdown, args=(10,))
    p1.start()

    p2 = multiprocessing.Process(target=countdown, args=(20,))
    p2.start()

    p1.join()
    p2.join()

Tutaj każda funkcja jest wykonywana w nowym procesie. Ponieważ nowa instancja Python VM uruchamia kod, nie ma GIL a równoległość działa na wielu rdzeniach.

Metoda Process.start uruchamia ten nowy proces i uruchamia funkcję przekazaną w argumencie target z argumentami args . Metoda Process.join czeka na zakończenie wykonywania procesów p1 i p2 .

Nowe procesy są uruchamiane w różny sposób w zależności od wersji Pythona i platformy, na której działa kod, np . :

  • System Windows używa spawn do utworzenia nowego procesu.
  • W systemach uniksowych i wersji wcześniejszej niż 3.3 procesy są tworzone za pomocą fork .
    Należy zauważyć, że ta metoda nie uwzględnia użycia rozwidlenia POSIX, a zatem prowadzi do nieoczekiwanych zachowań, szczególnie podczas interakcji z innymi bibliotekami wieloprocesowymi.
  • W systemie uniksowym i wersji 3.4+ możesz rozpocząć nowe procesy za pomocą fork , forkserver lub spawn używając multiprocessing.set_start_method na początku programu. metody forkserver i spawn są wolniejsze niż rozwidlanie, ale unikają nieoczekiwanych zachowań.

Zastosowanie widelca POSIX :

Po rozwidleniu w programie wielowątkowym dziecko może bezpiecznie wywoływać tylko funkcje bezpieczne dla sygnału asynchronicznego do czasu wykonania wywołania.
( patrz )

Korzystając z fork, zostanie uruchomiony nowy proces z dokładnie tym samym stanem dla wszystkich bieżących muteksów, ale MainThread zostanie tylko MainThread . Jest to niebezpieczne, ponieważ może prowadzić do warunków wyścigu, np . :

  • Jeśli użyjesz Lock w MainThread i przekażesz ją do innego wątku, który w pewnym momencie powinien ją zablokować. Jeśli fork jednocześnie, nowy proces rozpocznie się od zablokowanej blokady, która nigdy nie zostanie zwolniona, ponieważ drugi wątek nie istnieje w tym nowym procesie.

W rzeczywistości tego rodzaju zachowanie nie powinno występować w czystym pythonie, ponieważ multiprocessing obsługuje go poprawnie, ale jeśli wchodzisz w interakcję z inną biblioteką, takie zachowanie może wystąpić, prowadząc do awarii systemu (na przykład z numpy / przyspieszenia na macOS).

Przekazywanie danych między procesami wieloprocesowymi

Ponieważ dane są wrażliwe, gdy są przetwarzane między dwoma wątkami (myśl, że jednoczesny odczyt i jednoczesny zapis mogą kolidować ze sobą, powodując warunki wyścigu), stworzono zestaw unikalnych obiektów w celu ułatwienia przesyłania danych w obie strony. Każdą operację naprawdę atomową można zastosować między wątkami, ale zawsze można bezpiecznie trzymać się kolejki.

import multiprocessing
import queue
my_Queue=multiprocessing.Queue() 
#Creates a queue with an undefined maximum size
#this can be dangerous as the queue becomes increasingly large
#it will take a long time to copy data to/from each read/write thread

Większość osób sugeruje, aby podczas korzystania z kolejki zawsze umieszczać dane kolejki w try: oprócz: blok zamiast używać pustych. Jednak w przypadku aplikacji, w których nie ma znaczenia, czy pominiesz cykl skanowania (dane mogą być umieszczane w kolejce podczas odwracania stanów z queue.Empty==True do queue.Empty==False ) zwykle lepiej jest umieścić odczyt i dostęp do zapisu w tak zwanym bloku Iftry, ponieważ instrukcja „if” jest technicznie bardziej wydajna niż wyłapanie wyjątku.

import multiprocessing
import queue
'''Import necessary Python standard libraries, multiprocessing for classes and queue for the queue exceptions it provides'''
def Queue_Iftry_Get(get_queue, default=None, use_default=False, func=None, use_func=False):
    '''This global method for the Iftry block is provided for it's reuse and 
standard functionality, the if also saves on performance as opposed to catching
 the exception, which is expencive.
        It also allows the user to specify a function for the outgoing data to use,
 and a default value to return if the function cannot return the value from the queue'''
        if get_queue.empty():
            if use_default:
                return default
        else:
            try:
                value = get_queue.get_nowait()
            except queue.Empty:
                if use_default:
                    return default
            else:
                if use_func:
                    return func(value)
                else:
                    return value
    def Queue_Iftry_Put(put_queue, value):
        '''This global method for the Iftry block is provided because of its reuse 
and 
standard functionality, the If also saves on performance as opposed to catching
 the exception, which is expensive.
        Return True if placing value in the queue was successful. Otherwise, false'''
        if put_queue.full():
            return False
        else:
            try:
                put_queue.put_nowait(value)
            except queue.Full:
                return False
            else:
                return True


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow