Razprave, ki kritizirajo Python, pogosto govorijo o tem, kako težko je uporabljati Python za večnitno delo, s prstom kažejo na tako imenovano globalno zaklepanje tolmačev (ljubkovalno imenovano GIL ), ki preprečuje istočasno izvajanje več niti kode Python. Zaradi tega se večnitni modul Python ne obnaša povsem tako, kot bi pričakovali, če niste Razvijalec Python in prihajate iz drugih jezikov, kot sta C ++ ali Java. Jasno je treba poudariti, da lahko v Pythonu še vedno pišemo kodo, ki deluje hkrati ali vzporedno, in močno vplivamo na rezultat, če upoštevamo nekatere stvari. Če ga še niste prebrali, predlagam, da si ogledate Eqbal Quran's članek o sočasnosti in vzporednosti v Rubyju tukaj na spletnem dnevniku ApeeScape Engineering.
V tej vadnici za sočasnost Pythona bomo napisali majhen skript za Python, da bomo lahko prenesli najbolj priljubljene slike iz Imgurja. Začeli bomo z različico, ki slike prenaša zaporedno ali eno za drugo. Kot predpogoj se boste morali registrirati prijava na Imgurju . Če že nimate računa Imgur, ga najprej ustvarite.
Skripte v teh primerih navojev so bile preizkušene s Pythonom 3.6.4. Z nekaterimi spremembami bi se morali izvajati tudi s Pythonom 2 - urllib je tisto, kar se je najbolj spremenilo med tema dvema različicama Pythona.
Začnimo z ustvarjanjem modula Python z imenom download.py
. Ta datoteka bo vsebovala vse funkcije, potrebne za pridobitev seznama slik in njihovo nalaganje. Te funkcionalnosti bomo razdelili na tri ločene funkcije:
get_links
download_link
setup_download_dir
Tretja funkcija, setup_download_dir
, bo uporabljena za ustvarjanje ciljnega imenika za prenos, če ta še ne obstaja.
Imgurjev API zahteva, da zahteve HTTP vsebujejo Authorization
glava z ID-jem odjemalca. Ta ID odjemalca najdete na nadzorni plošči aplikacije, ki ste jo registrirali na Imgurju, odgovor pa bo kodiran v obliki JSON. Za njegovo dekodiranje lahko uporabimo Pythonovo standardno knjižnico JSON. Prenos slike je še enostavnejša naloga, saj je vse, kar morate storiti, da sliko po njenem URL-ju zapišete v datoteko.
Takole je videti skript:
import json import logging import os from pathlib import Path from urllib.request import urlopen, Request logger = logging.getLogger(__name__) types = {'image/jpeg', 'image/png'} def get_links(client_id): headers = {'Authorization': 'Client-ID {}'.format(client_id)} req = Request('https://api.imgur.com/3/gallery/random/random/', headers=headers, method='GET') with urlopen(req) as resp: data = json.loads(resp.read().decode('utf-8')) return [item['link'] for item in data['data'] if 'type' in item and item['type'] in types] def download_link(directory, link): download_path = directory / os.path.basename(link) with urlopen(link) as image, download_path.open('wb') as f: f.write(image.read()) logger.info('Downloaded %s', link) def setup_download_dir(): download_dir = Path('images') if not download_dir.exists(): download_dir.mkdir() return download_dir
Nato bomo morali napisati modul, ki bo te funkcije uporabljal za prenos slik, eno za drugo. To bomo poimenovali single.py
. Ta bo vsebovala glavno funkcijo naše prve, naivne različice programa za prenos slik Imgur. Modul bo pridobil ID odjemalca Imgur v spremenljivki okolja IMGUR_CLIENT_ID
. Priklicalo bo setup_download_dir
da ustvarite ciljni imenik za prenos. Končno bo pokazal seznam slik z get_links
funkcijo, filtrirajte vse URL-je GIF in albumov ter nato uporabite download_link
da naložite in shranite vsako od teh slik na disk. Tukaj je single.py
izgleda kot:
import logging import os from time import time from download import setup_download_dir, get_links, download_link logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) def main(): ts = time() client_id = os.getenv('IMGUR_CLIENT_ID') if not client_id: raise Exception('Couldn't find IMGUR_CLIENT_ID environment variable!') download_dir = setup_download_dir() links = get_links(client_id) for link in links: download_link(download_dir, link) logging.info('Took %s seconds', time() - ts) if __name__ == '__main__': main()
V mojem prenosniku je ta skript trajal 19,4 sekunde, da je prenesel 91 slik. Upoštevajte, da se te številke lahko razlikujejo glede na omrežje, v katerem ste. 19,4 sekunde ni strašno dolgo, a kaj, če bi želeli prenesti več slik? Morda 900 slik, namesto 90. S povprečno 0,2 sekunde na sliko bi 900 slik trajalo približno 3 minute. Za 9000 slik bi trajalo 30 minut. Dobra novica je, da lahko z uvedbo sočasnosti ali vzporednosti to močno pospešimo.
Vsi nadaljnji primeri kode bodo prikazali samo izjave o uvozu, ki so nove in specifične za te primere. Za udobje lahko vse te skripte Python najdete v to skladišče GitHub .
Threading je eden najbolj znanih pristopov k doseganju Pythonove sočasnosti in vzporednosti. Threading je funkcija, ki jo običajno zagotavlja operacijski sistem. Niti so lažji od procesov in imajo isti pomnilniški prostor.
V tem primeru navojev navojev Python bomo napisali nov modul, ki bo nadomestil single.py
Ta modul bo ustvaril skupino osmih niti, kar bo skupaj devet glavnih niti, vključno z glavno nitjo. Izbral sem osem delovnih niti, ker ima moj računalnik osem jeder procesorja in ena delovna nit na jedro se je zdela dobra številka za to, koliko niti naj teče hkrati. V praksi je to število izbrano veliko bolj skrbno glede na druge dejavnike, na primer druge aplikacije in storitve, ki se izvajajo na istem računalniku.
Ta je skoraj enak prejšnjemu, z izjemo, da imamo zdaj nov razred, DownloadWorker
, ki je potomec Pythona Thread
razred. Preglašena je bila metoda zagon, ki izvaja neskončno zanko. Pri vsaki ponovitvi pokliče self.queue.get()
da poskusite pridobiti URL do iz čakalne vrste, ki je varna za nit. Blokira, dokler v čakalni vrsti ni elementa, ki ga mora delavec obdelati. Ko delavec prejme element iz čakalne vrste, nato pokliče isto download_link
metoda, ki je bila uporabljena v prejšnjem skriptu za prenos slike v imenik slik. Po končanem prenosu delavec signalizira čakalni vrsti, da je ta naloga opravljena. To je zelo pomembno, saj čakalna vrsta spremlja, koliko nalog je bilo postavljenih v vrsto. Klic na queue.join()
bi za vedno blokiral glavno nit, če delavci ne bi signalizirali, da so opravili nalogo.
import logging import os from queue import Queue from threading import Thread from time import time from download import setup_download_dir, get_links, download_link logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) class DownloadWorker(Thread): def __init__(self, queue): Thread.__init__(self) self.queue = queue def run(self): while True: # Get the work from the queue and expand the tuple directory, link = self.queue.get() try: download_link(directory, link) finally: self.queue.task_done() def main(): ts = time() client_id = os.getenv('IMGUR_CLIENT_ID') if not client_id: raise Exception('Couldn't find IMGUR_CLIENT_ID environment variable!') download_dir = setup_download_dir() links = get_links(client_id) # Create a queue to communicate with the worker threads queue = Queue() # Create 8 worker threads for x in range(8): worker = DownloadWorker(queue) # Setting daemon to True will let the main thread exit even though the workers are blocking worker.daemon = True worker.start() # Put the tasks into the queue as a tuple for link in links: logger.info('Queueing {}'.format(link)) queue.put((download_dir, link)) # Causes the main thread to wait for the queue to finish processing all the tasks queue.join() logging.info('Took %s', time() - ts) if __name__ == '__main__': main()
Zagon tega primera skripta navojev Python na istem računalniku, ki je bil uporabljen prej, povzroči prenos časa 4,1 sekunde! To je 4,7-krat hitreje kot v prejšnjem primeru. Čeprav je to veliko hitrejše, je treba omeniti, da se je ves čas tega postopka zaradi GIL izvajala le ena nit. Zato je ta koda sočasna, vendar ne vzporedna. Razlog, da je še vedno hitrejši, je, da je to naloga, vezana na IO. Procesor se med prenosom teh slik skoraj ne znoji in večino časa porabi za čakanje na omrežje. To je razlog, zakaj lahko večnitnost Python poveča hitrost. Procesor lahko preklaplja med nitmi, kadar je katera od njih pripravljena za nekaj dela. Uporaba modula navojev v Pythonu ali katerem koli drugem interpretiranem jeziku z GIL lahko dejansko povzroči manjšo zmogljivost. Če vaša koda izvaja nalogo, vezano na CPU, na primer razpakiranje datotek gzip, z uporabo threading
modul bo povzročil počasnejši čas izvajanja. Za naloge, vezane na CPU, in resnično vzporedno izvajanje lahko uporabimo večprocesorski modul.
Medtem ko de facto sklic na izvedbo Pythona - CPython - ima GIL, to ne velja za vse izvedbe Pythona. Na primer, IronPython, izvedba Pythona, ki uporablja ogrodje .NET, nima GIL, prav tako pa tudi Jython, ki temelji na Javi. Tu lahko najdete seznam delujočih implementacij Pythona tukaj .
Sorodno: Najboljši postopki in nasveti za Python razvijalcev ApeeScapeModul za večprocesorsko obdelavo je lažje vstopiti kot modul za navoja, saj nam ni treba dodati razreda, kot je primer navojev Python. Edine spremembe, ki jih moramo narediti, so v glavni funkciji.
Za uporabo več procesov ustvarimo večprocesorsko Pool
. Z metodo map, ki jo ponuja, bomo seznam URL-jev poslali v bazen, ta pa bo ustvaril osem novih procesov in uporabil vsakega za vzporedni prenos slik. To je resnična paralelizem, vendar to prinaša stroške. Celoten pomnilnik skripta se kopira v vsak podproces, ki se ustvari. V tem preprostem primeru ni veliko, vendar lahko za netrivialne programe zlahka postane režija.
import logging import os from functools import partial from multiprocessing.pool import Pool from time import time from download import setup_download_dir, get_links, download_link logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logging.getLogger('requests').setLevel(logging.CRITICAL) logger = logging.getLogger(__name__) def main(): ts = time() client_id = os.getenv('IMGUR_CLIENT_ID') if not client_id: raise Exception('Couldn't find IMGUR_CLIENT_ID environment variable!') download_dir = setup_download_dir() links = get_links(client_id) download = partial(download_link, download_dir) with Pool(4) as p: p.map(download, links) logging.info('Took %s seconds', time() - ts) if __name__ == '__main__': main()
Čeprav so moduli za navojev in večprocesorsko obdelavo odlični za skripte, ki se izvajajo v vašem osebnem računalniku, kaj storiti, če želite, da se delo opravi na drugem računalniku ali če morate povečati število procesorjev na več kot en CPU ročaj? Odličen primer tega so dolgotrajna zaledna opravila za spletne aplikacije. Če imate nekaj dolgotrajnih nalog, ne želite na istem računalniku zasukati kopice podprocesov ali niti, ki morajo izvajati preostalo kodo aplikacije. To bo poslabšalo delovanje vaše aplikacije za vse vaše uporabnike. Odlično bi bilo, če bi lahko ta opravila izvajali na drugem ali mnogih drugih strojih.
Odlična knjižnica Python za to nalogo je RQ , zelo preprosta, a zmogljiva knjižnica. Funkcijo in njene argumente najprej uvrstite v knjižnico. To kumarice predstavitev klica funkcije, ki je nato dodana a Redis seznam. Prvi korak je čakanje na čakalno vrsto, vendar še ničesar ne boste storili. Prav tako potrebujemo vsaj enega delavca, ki bo prisluhnil tej čakalni vrsti.
Prvi korak je namestitev in zagon strežnika Redis v računalniku ali dostop do delujočega strežnika Redis. Po tem je le nekaj majhnih sprememb obstoječe kode. Najprej izdelamo primerek RQ Queue in mu posredujemo primerek strežnika Redis iz knjižnica redis-py . Potem, namesto da samo pokličete našo download_link
metoda, imenujemo q.enqueue(download_link, download_dir, link)
. Metoda čakalne vrste sprejme funkcijo kot prvi argument, nato pa se kateri koli drugi argumenti ali argumenti ključnih besed posredujejo tej funkciji, ko je opravilo dejansko izvedeno.
Zadnji korak, ki ga moramo narediti, je zagon nekaterih delavcev. RQ ponuja priročen skript za zagon delavcev v privzeti čakalni vrsti. Samo zaženite rqworker
v terminalskem oknu in bo zagnal delavca, ki posluša v privzeti vrsti. Prepričajte se, da je vaš trenutni delovni imenik enak tistemu, v katerem so skripti. Če želite poslušati drugo vrsto, lahko zaženete rqworker queue_name
in poslušalo bo to imenovano vrsto. Odlična stvar pri RQ je, da dokler se lahko povežete z Redisom, lahko na poljubnih različnih strojih izvajate toliko delavcev, kot želite; zato ga je zelo enostavno povečati, ko raste vaša aplikacija. Tu je vir za različico RQ:
import logging import os from redis import Redis from rq import Queue from download import setup_download_dir, get_links, download_link logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logging.getLogger('requests').setLevel(logging.CRITICAL) logger = logging.getLogger(__name__) def main(): client_id = os.getenv('IMGUR_CLIENT_ID') if not client_id: raise Exception('Couldn't find IMGUR_CLIENT_ID environment variable!') download_dir = setup_download_dir() links = get_links(client_id) q = Queue(connection=Redis(host='localhost', port=6379)) for link in links: q.enqueue(download_link, download_dir, link) if __name__ == '__main__': main()
Vendar RQ ni edina rešitev čakalne vrste Python. RQ je enostaven za uporabo in izjemno dobro pokriva primere enostavne uporabe, če pa so potrebne naprednejše možnosti, druge rešitve čakalne vrste Python 3 (kot je npr. Zelena ) je lahko uporabljen.
Če je vaša koda vezana na IO, bosta večprocesorska in večnitna obdelava v Pythonu delovala za vas. Večprocesorsko obdelavo je lažje vstaviti kot navoj, vendar ima večji režijski pomnilnik. Če je vaša koda vezana na CPU, bo najverjetneje boljša izbira večprocesorska obdelava - še posebej, če ima ciljni računalnik več jeder ali CPU-jev. Za spletne aplikacije in ko boste morali delo prilagoditi več strojem, bo RQ boljši za vas.
Sorodno: Postanite bolj napredni: izogibajte se 10 najpogostejšim napakam programerjev Pythonconcurrent.futures
Nekaj novega od Pythona 3.2, česar se prvotni članek ni dotaknil, je concurrent.futures
paket. Ta paket ponuja še en način za uporabo sočasnosti in vzporednosti s Pythonom.
V prvotnem članku sem omenil, da bi Pythonov večprocesorski modul lažje spustil v obstoječo kodo kot modul za navoje. To je bilo zato, ker je modul navojev Python 3 zahteval podrazvrstitev Thread
razred in tudi ustvarjanje Queue
za niti, ki jih lahko spremljate za delo.
Uporaba a concurrent.futures.ThreadPoolExecutor naredi primer kode navojev Python skoraj enak večprocesorskemu modulu.
import logging import os from concurrent.futures import ThreadPoolExecutor from functools import partial from time import time from download import setup_download_dir, get_links, download_link logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) def main(): client_id = os.getenv('IMGUR_CLIENT_ID') if not client_id: raise Exception('Couldn't find IMGUR_CLIENT_ID environment variable!') download_dir = setup_download_dir() links = get_links(client_id) # By placing the executor inside a with block, the executors shutdown method # will be called cleaning up threads. # # By default, the executor sets number of workers to 5 times the number of # CPUs. with ThreadPoolExecutor() as executor: # Create a new partially applied function that stores the directory # argument. # # This allows the download_link function that normally takes two # arguments to work with the map function that expects a function of a # single argument. fn = partial(download_link, download_dir) # Executes fn concurrently using threads on the links iterable. The # timeout is for the entire process, not a single call, so downloading # all images must complete within 30 seconds. executor.map(fn, links, timeout=30) if __name__ == '__main__': main()
Zdaj, ko smo vse te slike prenesli z našim Pythonom ThreadPoolExecutor
, jih lahko uporabimo za testiranje naloge, vezane na CPU. Ustvarimo lahko sličice različic vseh slik v enonitnem skriptu z enim procesom in nato preizkusimo rešitev, ki temelji na več obdelavah.
Uporabili bomo Vzglavnik knjižnico za obdelavo velikosti slik.
Tu je naš začetni skript.
import logging from pathlib import Path from time import time from PIL import Image logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) def create_thumbnail(size, path): ''' Creates a thumbnail of an image with the same name as image but with _thumbnail appended before the extension. E.g.: >>> create_thumbnail((128, 128), 'image.jpg') A new thumbnail image is created with the name image_thumbnail.jpg :param size: A tuple of the width and height of the image :param path: The path to the image file :return: None ''' image = Image.open(path) image.thumbnail(size) path = Path(path) name = path.stem + '_thumbnail' + path.suffix thumbnail_path = path.with_name(name) image.save(thumbnail_path) def main(): ts = time() for image_path in Path('images').iterdir(): create_thumbnail((128, 128), image_path) logging.info('Took %s', time() - ts) if __name__ == '__main__': main()
Ta skript ponovi poti v images
mapo in za vsako pot zažene funkcijo create_thumbnail. Ta funkcija uporablja vzglavnik, da odpre sliko, ustvari sličico in shrani novo, manjšo sliko z istim imenom kot izvirnik, vendar z _thumbnail
priloženo k imenu.
Zagon tega skripta na 160 slikah v skupni vrednosti 36 milijonov traja 2,32 sekunde. Poglejmo, ali lahko to pospešimo z uporabo ProcessPoolExecutor .
import logging from pathlib import Path from time import time from functools import partial from concurrent.futures import ProcessPoolExecutor from PIL import Image logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) def create_thumbnail(size, path): ''' Creates a thumbnail of an image with the same name as image but with _thumbnail appended before the extension. E.g.: >>> create_thumbnail((128, 128), 'image.jpg') A new thumbnail image is created with the name image_thumbnail.jpg :param size: A tuple of the width and height of the image :param path: The path to the image file :return: None ''' path = Path(path) name = path.stem + '_thumbnail' + path.suffix thumbnail_path = path.with_name(name) image = Image.open(path) image.thumbnail(size) image.save(thumbnail_path) def main(): ts = time() # Partially apply the create_thumbnail method, setting the size to 128x128 # and returning a function of a single argument. thumbnail_128 = partial(create_thumbnail, (128, 128)) # Create the executor in a with block so shutdown is called when the block # is exited. with ProcessPoolExecutor() as executor: executor.map(thumbnail_128, Path('images').iterdir()) logging.info('Took %s', time() - ts) if __name__ == '__main__': main()
The create_thumbnail
metoda je enaka zadnji skripti. Glavna razlika je v ustvarjanju ProcessPoolExecutor
. Izvršiteljev zemljevid metoda se uporablja za vzporedno ustvarjanje sličic. Privzeto je ProcessPoolExecutor
ustvari en podproces na CPU. Zagon tega skripta na istih 160 slikah je trajal 1,05 sekunde - 2,2-krat hitreje!
Eden najbolj zahtevanih elementov v komentarjih na izvirni članek je bil na primer uporaba Pythona 3 asincio modul. V primerjavi z drugimi primeri obstaja nekaj novih sintaks Python, ki so morda za večino ljudi nove in tudi nekateri novi koncepti. Nesrečno dodatno plast zapletenosti povzroča Pythonova vgrajena urllib
modul ni asinhron. Za popolne prednosti asyncia bomo morali uporabiti async knjižnico HTTP. Za to bomo uporabili aiohttp .
Skočimo naravnost v kodo in sledila bo podrobnejša razlaga.
import asyncio import logging import os from time import time import aiohttp from download import setup_download_dir, get_links logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) async def async_download_link(session, directory, link): ''' Async version of the download_link method we've been using in the other examples. :param session: aiohttp ClientSession :param directory: directory to save downloads :param link: the url of the link to download :return: ''' download_path = directory / os.path.basename(link) async with session.get(link) as response: with download_path.open('wb') as f: while True: # await pauses execution until the 1024 (or less) bytes are read from the stream chunk = await response.content.read(1024) if not chunk: # We are done reading the file, break out of the while loop break f.write(chunk) logger.info('Downloaded %s', link) # Main is now a coroutine async def main(): client_id = os.getenv('IMGUR_CLIENT_ID') if not client_id: raise Exception('Couldn't find IMGUR_CLIENT_ID environment variable!') download_dir = setup_download_dir() # We use a session to take advantage of tcp keep-alive # Set a 3 second read and connect timeout. Default is 5 minutes async with aiohttp.ClientSession(conn_timeout=3, read_timeout=3) as session: tasks = [(async_download_link(session, download_dir, l)) for l in get_links(client_id)] # gather aggregates all the tasks and schedules them in the event loop await asyncio.gather(*tasks, return_exceptions=True) if __name__ == '__main__': ts = time() # Create the asyncio event loop loop = asyncio.get_event_loop() try: loop.run_until_complete(main()) finally: # Shutdown the loop even if there is an exception loop.close() logger.info('Took %s seconds to complete', time() - ts)
Tu je treba kar nekaj razpakirati. Začnimo z glavno vstopno točko programa. Prva nova stvar, ki jo naredimo z modulom asyncio, je pridobiti zanko dogodka. Zanka dogodkov obravnava vso asinhrono kodo. Nato se zanka zažene, dokler ni dokončana in posreduje main
funkcijo. V definiciji main je del nove sintakse: async def
. Opazili boste tudi await
in with async
.
Sintaksa async / await je bila uvedena v PEP492 . The async def
sintaksa označuje funkcijo kot koroutina . Notranji programi temeljijo na Pythonovih generatorjih, vendar niso povsem ista stvar. Programi vrnejo podprogramski objekt, podobno kot generatorji vrnejo generatorski objekt. Ko dobite podprogram, rezultate dobite z await
izraz. Ko podprogram pokliče await
, se izvajanje podprograma začasno ustavi, dokler se pričakovano ne konča. Ta prekinitev omogoča dokončanje drugega dela, medtem ko je koroutina začasno ustavljena, 'v čakanju' na nek rezultat. Na splošno bo to rezultat neke vrste V / I, kot je zahteva za bazo podatkov ali v našem primeru zahteva HTTP.
The download_link
funkcijo je bilo treba precej spremeniti. Prej smo se zanašali na urllib
da bomo za nas prebrali breme pri branju slike. Da bi naši metodi omogočili pravilno delovanje s paradigmo asinhronega programiranja, smo predstavili while
zanka, ki naenkrat bere koščke slike in začasno ustavi izvajanje, medtem ko čaka na dokončanje V / I. To omogoča zanki dogodka, da se vrti skozi prenos različnih slik, saj ima vsaka med prenosom na voljo nove podatke.
Medtem ko zen Pythona nam pove, da bi moral obstajati en očiten način, da v Pythonu obstaja veliko načinov za uvedbo sočasnosti v naše programe. Najboljši način izbire bo odvisen od vašega primera uporabe. Asinhrona paradigma se bolje prilagaja delovnim obremenitvam z visoko hkratnostjo (kot je spletni strežnik) v primerjavi z navoji ali večprocesorskimi obdelavami, vendar zahteva, da je vaša koda (in odvisnosti) asinhronizirana, da lahko v celoti izkoristi.
Upajmo, da vas bodo primeri navojev Python v tem članku - in posodobitve - usmerili v pravo smer, tako da boste imeli idejo, kje iskati v standardni knjižnici Python, če želite v svoje programe vnesti sočasnost.
Nit je lahek postopek ali naloga. Nit je eden od načinov za dodajanje sočasnosti vašim programom. Če vaša aplikacija Python uporablja več niti in pogledate procese, ki se izvajajo v vašem OS, boste videli samo en vnos za svoj skript, čeprav ima več niti.
Večnitnost (včasih preprosto »navoj«) je, ko program ustvari več niti z izvedbenim ciklom med njimi, zato ena dolgotrajnejša naloga ne blokira vseh ostalih. To dobro deluje pri nalogah, ki jih lahko razdelimo na manjše podopravila, ki jih nato lahko damo v nit, ki jo je treba dokončati.
Z navojem navojev sočasno dosežemo z več nitmi, vendar se zaradi GIL-a lahko izvaja samo ena nit naenkrat. Pri večprocesorski obdelavi je prvotni postopek razdeljen na več podrejenih procesov, mimo GIL. Vsak podrejeni postopek bo imel kopijo celotnega pomnilnika programa.
Večnitnost in večprocesorska obdelava omogočata sočasno izvajanje kode Python. Samo večprocesorska obdelava bo omogočila, da bo vaša koda resnično vzporedna. Če pa je vaša koda IO težka (na primer zahteve HTTP), bo večnitnost še vedno verjetno pospešila kodo.