Sök…


Anmärkningar

Python-utvecklarna såg till att API mellan threading och multiprocessing liknar så att det är lättare att växla mellan de två varianterna för programmerare.

Trådmodulen

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

I vissa implementationer av Python, såsom CPython, uppnås inte sann parallellitet med hjälp av trådar på grund av att använda det som kallas GIL, eller G lobal I nterpreter L ock.

Här är en utmärkt översikt av Python samtidighet:

Python samtidighet av David Beazley (YouTube)

Multiprocesseringsmodulen

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

Här utförs varje funktion i en ny process. Eftersom en ny instans av Python VM kör koden finns det ingen GIL och du får parallellitet på flera kärnor.

De Process.start metoden lanserar denna nya process och kör funktionen passerade i target argumentet med argument args . Process.join metoden väntar på slutet av exekveringen av processerna p1 och p2 .

De nya processerna lanseras annorlunda beroende på versionen av python och plattformen som koden körs på, t.ex.

  • Windows använder spawn att skapa den nya processen.
  • Med unix-system och version tidigare än 3.3 skapas processerna med en fork .
    Observera att den här metoden inte respekterar POSIX-användningen av gaffel och därmed leder till oväntat beteende, särskilt när du interagerar med andra multiprocesserbibliotek.
  • Med unix-system och version 3.4+ kan du välja att starta de nya processerna med antingen fork , forkserver eller spawn med multiprocessing.set_start_method i början av programmet. forkserver och spawn metoder är långsammare än forking men undviker oväntat beteende.

POSIX gaffelanvändning :

Efter en gaffel i ett multithread-program kan barnet säkert bara ringa async-signal-säkra funktioner tills det samtalet körs.
( se )

Med gaffel kommer en ny process att startas med exakt samma tillstånd för all den aktuella mutex men bara MainThread kommer att startas. Detta är osäkert eftersom det kan leda till tävlingsförhållanden, t.ex.

  • Om du använder ett Lock i MainThread och MainThread det till en annan tråd som antas låsa det vid någon tidpunkt. Om fork sker samtidigt börjar den nya processen med ett låst lås som aldrig kommer att släppas eftersom den andra tråden inte finns i den nya processen.

Egentligen borde denna typ av beteende inte uppstå i ren python eftersom multiprocessing hanterar det ordentligt, men om du interagerar med andra bibliotek kan denna typ av uppträdande inträffa, vilket kan leda till att ditt system kraschar (till exempel med numpy / accelererad på macOS).

Vidarebefordra data mellan multiprocesseringsprocesser

Eftersom data är känsliga när de hanteras mellan två trådar (tänk samtidigt läsning och samtidig skrivning kan komma i konflikt med varandra och orsaka rasförhållanden) gjordes en uppsättning unika objekt för att underlätta överföring av data fram och tillbaka mellan trådar. Alla verkliga atomoperationer kan användas mellan trådar, men det är alltid säkert att hålla sig till kö.

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

De flesta kommer att föreslå att när du använder kö, alltid ska du placera ködata i ett försök: förutom: blockera istället för att använda tomt. Men för applikationer där det inte spelar någon roll om du hoppar över en skanningscykel (data kan placeras i kön medan det vänder tillstånd från queue.Empty==True till queue.Empty==False ) är det vanligtvis bättre att läsa och skrivåtkomst i det jag kallar ett Iftry-block, eftersom ett "if" -uttalande är tekniskt mer performant än att fånga undantaget.

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow