Programski jezik python omogućuje vam upotrebu višestruke obrade ili višestruke obrade niti. U ovom vodiču naučit ćete kako pisati višenitne programe na Pythonu.
Što je nit?
Nit je jedinica za izvođenje pri istodobnom programiranju. Multithreading je tehnika koja omogućuje CPU-u da istovremeno izvršava mnoge zadatke jednog procesa. Te se niti mogu izvršavati pojedinačno dok dijele svoje resurse procesa.
Što je proces?
Proces je u osnovi program u izvršenju. Kada na računalu pokrenete aplikaciju (poput preglednika ili uređivača teksta), operativni sustav kreira postupak.
Što je Multithreading u Pythonu?
Višenitnost u programiranju na Pythonu dobro je poznata tehnika u kojoj više niti u procesu dijeli svoj podatkovni prostor s glavnom niti što čini razmjenu informacija i komunikaciju unutar niti lakom i učinkovitom. Niti su lakši od procesa. Više niti mogu se izvršavati pojedinačno dok dijele svoje resurse procesa. Svrha multithreading-a je istodobno pokretanje više zadataka i funkcijskih ćelija.
Što je višeprocesiranje?
Višeprocesiranje omogućuje istodobno pokretanje više nepovezanih procesa. Ti procesi ne dijele svoje resurse i komuniciraju putem IPC-a.
Python Multithreading vs Multiprocessing
Da biste razumjeli procese i niti, razmotrite ovaj scenarij: .exe datoteka na vašem računalu je program. Kad ga otvorite, OS ga učita u memoriju, a CPU ga izvršava. Instanca programa koji je sada pokrenut naziva se proces.
Svaki će postupak imati dvije osnovne komponente:
- Kod
- Podatak
Sada postupak može sadržavati jedan ili više poddjelova koji se nazivaju niti. To ovisi o arhitekturi OS-a,. Možete nit smatrati dijelom procesa koji operativni sustav može zasebno izvršiti.
Drugim riječima, to je tok uputa koje OS može samostalno pokretati. Niti unutar jednog procesa dijele podatke tog procesa i osmišljeni su tako da zajedno rade na olakšavanju paralelizma.
U ovom ćete tutorijalu naučiti,
- Što je nit?
- Što je proces?
- Što je Multithreading?
- Što je višeprocesiranje?
- Python Multithreading vs Multiprocessing
- Zašto koristiti Multithreading?
- Python MultiThreading
- Moduli Thread i Threading
- Modul niti
- Modul navoja
- Zastoji i uvjeti utrke
- Sinkroniziranje niti
- Što je GIL?
- Zašto je bio potreban GIL?
Zašto koristiti Multithreading?
Multithreading omogućuje raščlambu aplikacije na više podzadataka i istodobno pokretanje tih zadataka. Ako pravilno upotrebljavate multithreading, brzina, izvedba i prikazivanje aplikacije mogu se poboljšati.
Python MultiThreading
Python podržava konstrukcije kako za višeprocesiranje, tako i za višestruko obrađivanje niti. U ovom vodiču prvenstveno ćete se usredotočiti na implementaciju višenitnih aplikacija s pythonom. Postoje dva glavna modula koja se mogu koristiti za obradu niti u Pythonu:
- Modul niti i
- Navoja modul
Međutim, u pythonu postoji i nešto što se naziva globalna brava tumača (GIL). Ne dopušta puno poboljšanje performansi, a može čak i smanjiti performanse nekih višenitnih aplikacija. Sve ćete o tome naučiti u sljedećim odjeljcima ovog vodiča.
Moduli Thread i Threading
Dva modula koje ćete naučiti o tome u ovom tutorial su nit modul i navoja modul .
Međutim, modul niti odavno je zastario. Počevši od Pythona 3, označen je zastarjelim i dostupan je samo kao __thread radi povratne kompatibilnosti.
Trebali biste koristiti modul navoja više razine za aplikacije koje namjeravate implementirati. Modul niti ovdje je pokriven samo u obrazovne svrhe.
Modul niti
Sintaksa za stvaranje nove niti pomoću ovog modula je sljedeća:
thread.start_new_thread(function_name, arguments)
Dobro, sad ste pokrili osnovnu teoriju za početak kodiranja. Dakle, otvorite svoj IDLE ili bilježnicu i unesite sljedeće:
import timeimport _threaddef thread_test(name, wait):i = 0while i <= 3:time.sleep(wait)print("Running %s\n" %name)i = i + 1print("%s has finished execution" %name)if __name__ == "__main__":_thread.start_new_thread(thread_test, ("First Thread", 1))_thread.start_new_thread(thread_test, ("Second Thread", 2))_thread.start_new_thread(thread_test, ("Third Thread", 3))
Spremite datoteku i pritisnite F5 da biste pokrenuli program. Ako je sve napravljeno ispravno, ovo bi trebao biti izlaz:
O nadolazećim dionicama naučit ćete više o uvjetima utrke i kako se nositi s njima
OBJAŠNJENJE KODA
- Ove izjave uvoze modul vremena i niti koji se koriste za izvršavanje i odgađanje Python niti.
- Ovdje ste definirali funkciju nazvanu thread_test, koja će se pozivati metodom start_new_thread . Funkcija pokreće while petlju za četiri ponavljanja i ispisuje ime niti koja ju je pozvala. Jednom kad je iteracija završena, ispisuje poruku u kojoj se kaže da je nit završila izvršenje.
- Ovo je glavni dio vašeg programa. Ovdje jednostavno pozivate metodu start_new_thread s funkcijom thread_test kao argumentom.
To će stvoriti novu nit za funkciju koju proslijedite kao argument i započeti je izvršavanje. Imajte na umu da ovo možete zamijeniti (nit _ test) bilo kojom drugom funkcijom koju želite pokrenuti kao nit.
Modul navoja
Ovaj je modul implementacija navoja na visokoj razini u pythonu i de facto standard za upravljanje višenitnim aplikacijama. Pruža širok raspon značajki u usporedbi s modulom niti.
![](https://cdn.css-code.org/images/1/080219_0505_Multithread3.png.webp)
Evo popisa nekih korisnih funkcija definiranih u ovom modulu:
Naziv funkcije | Opis |
activeCount () | Vraća broj temu predmete koji su još uvijek živi |
currentThread () | Vraća trenutni objekt klase Thread. |
nabrojati() | Navodi sve aktivne objekte niti. |
isDaemon () | Vraća true ako je nit demon. |
živ je() | Vraća true ako je nit još uvijek živa. |
Metode klase niti | |
početak() | Pokreće aktivnost niti. Mora se pozvati samo jednom za svaku nit, jer će baciti pogrešku izvršavanja ako se pozove više puta. |
trčanje() | Ova metoda označava aktivnost niti i može je nadjačati klasa koja proširuje klasu Thread. |
pridružiti() | Blokira izvršavanje drugog koda dok se nit na kojoj je metoda join () pozvana ne završi. |
Predzadnja: klasa niti
Prije nego započnete s kodiranjem višenitnih programa pomoću modula za navoja, bitno je razumjeti klasu Thread. Klasa niti je primarna klasa koja definira predložak i operacije niti u pythonu.
Najčešći način stvaranja multithreaded python aplikacije je deklariranje klase koja proširuje klasu Thread i poništava metodu run ().
Klasa Thread u sažetku označava slijed koda koji se izvodi u zasebnoj niti kontrole.
Dakle, prilikom pisanja višenitne aplikacije učinit ćete sljedeće:
- definirati klasu koja proširuje klasu Thread
- Nadjačajte konstruktor __init__
- Zamijenite metodu run ()
Jednom kada je napravljen objekt niti, metoda start () može se koristiti za započinjanje izvršavanja ove aktivnosti, a metoda join () može se koristiti za blokiranje svih ostalih kodova dok trenutna aktivnost ne završi.
Pokušajmo sada koristiti modul za navoja za implementaciju vašeg prethodnog primjera. Opet, upalite svoj IDLE i unesite sljedeće:
import timeimport threadingclass threadtester (threading.Thread):def __init__(self, id, name, i):threading.Thread.__init__(self)self.id = idself.name = nameself.i = idef run(self):thread_test(self.name, self.i, 5)print ("%s has finished execution " %self.name)def thread_test(name, wait, i):while i:time.sleep(wait)print ("Running %s \n" %name)i = i - 1if __name__=="__main__":thread1 = threadtester(1, "First Thread", 1)thread2 = threadtester(2, "Second Thread", 2)thread3 = threadtester(3, "Third Thread", 3)thread1.start()thread2.start()thread3.start()thread1.join()thread2.join()thread3.join()
Ovo će biti izlaz kad izvršite gornji kod:
OBJAŠNJENJE KODA
- Ovaj je dio isti kao i naš prethodni primjer. Ovdje uvozite modul vremena i niti koji se koriste za rukovanje izvršenjem i kašnjenjima Python niti.
- U ovom bitu kreirate klasu koja se naziva threadtester, koja nasljeđuje ili proširuje klasu Thread modula za navoja. Ovo je jedan od najčešćih načina stvaranja niti u pythonu. Međutim, trebali biste nadjačati samo konstruktor i metodu run () u svojoj aplikaciji. Kao što možete vidjeti u gornjem uzorku koda, metoda __init__ (konstruktor) je nadjačana.
Slično tome, nadjačali ste i metodu run () . Sadrži kôd koji želite izvršiti unutar niti. U ovom primjeru pozvali ste funkciju thread_test ().
- Ovo je metoda thread_test () koja uzima vrijednost i kao argument, smanjuje je za 1 na svakoj iteraciji i petlja kroz ostatak koda dok i ne postane 0. U svakoj iteraciji ispisuje ime trenutačno izvršavajuće niti i spava sekunde čekanja (što se također uzima kao argument).
- thread1 = threadtester (1, "Prva nit", 1)
Ovdje stvaramo nit i prenosimo tri parametra koja smo deklarirali u __init__. Prvi parametar je ID niti, drugi parametar je ime niti, a treći parametar je brojač, koji određuje koliko puta treba pokrenuti loop.
- thread2.start ()
Metoda pokretanja koristi se za pokretanje izvođenja niti. Interno funkcija start () poziva metodu run () vaše klase.
- thread3.join ()
Metoda join () blokira izvršavanje drugog koda i čeka dok nit na kojoj je pozvan ne završi.
Kao što već znate, niti koje su u istom procesu imaju pristup memoriji i podacima tog procesa. Kao rezultat toga, ako više od jedne niti pokušava istodobno promijeniti ili pristupiti podacima, mogu se uvući pogreške.
U sljedećem ćete odjeljku vidjeti različite vrste komplikacija koje se mogu pojaviti kada niti pristupaju podacima i kritičnom odjeljku bez provjere postojećih pristupnih transakcija.
Zastoji i uvjeti utrke
Prije učenja o zastojima i uvjetima utrke, bilo bi korisno razumjeti nekoliko osnovnih definicija povezanih s istodobnim programiranjem:
- Kritični odjeljak
To je fragment koda koji pristupa ili mijenja zajedničke varijable i mora se izvesti kao atomska transakcija.
- Prebacivanje konteksta
To je proces koji CPU slijedi za pohranu stanja niti prije promjene s jednog zadatka na drugi kako bi se kasnije mogao nastaviti s iste točke.
Zastoji
Zastoji su najstrašniji problem s kojim se programeri susreću prilikom pisanja istodobnih / višenitnih aplikacija u pythonu. Najbolji način za razumijevanje zastoja jest korištenje klasičnog primjera informatičkog problema poznatog kao problem filozofije za ručavanje.
Izjava problema za filozofe blagovaonice je sljedeća:
Pet filozofa sjedi na okruglom stolu s pet tanjura špageta (vrsta tjestenine) i pet vilica, kako je prikazano na dijagramu.
![](https://cdn.css-code.org/images/1/080219_0505_Multithread6.png.webp)
U bilo kojem trenutku, filozof mora ili jesti ili razmišljati.
Štoviše, filozof mora uzeti dvije susjedne vilice (tj. Lijevu i desnu vilicu) prije nego što može pojesti špagete. Problem slijepe ulice javlja se kada svih pet filozofa istovremeno podignu svoje desne vilice.
Budući da svaki od filozofa ima po jednu vilicu, svi će pričekati da i drugi odlože vilicu. Kao rezultat toga, nitko od njih neće moći jesti špagete.
Slično tome, u istodobnom sustavu dolazi do zastoja kada različite niti ili procesi (filozofi) pokušavaju istodobno pribaviti zajedničke resurse sustava (rašlje). Kao rezultat toga, niti jedan proces nema priliku za izvršenje jer čeka drugi resurs koji drži neki drugi proces.
Uvjeti utrke
Utrka je neželjeno stanje programa koje se javlja kada sustav istodobno izvodi dvije ili više operacija. Na primjer, razmotrite ovu jednostavnu for petlju:
i=0; # a global variablefor x in range(100):print(i)i+=1;
Ako stvorite n broja niti koje istodobno pokreću ovaj kod, ne možete odrediti vrijednost i (koju dijele niti) kada program završi izvršenje. To je zato što se u stvarnom višenitnom okruženju niti mogu preklapati, a vrijednost i koju je nit dohvatio i izmijenio može se mijenjati između toga kada joj neka druga nit pristupa.
To su dvije glavne klase problema koji se mogu pojaviti u multithreading ili distribuiranoj python aplikaciji. U sljedećem ćete odjeljku naučiti kako prevladati taj problem sinkronizacijom niti.
Sinkroniziranje niti
Za rješavanje uvjeta utrke, zastoja i drugih problema temeljenih na nitima, modul za navoja nudi objekt Lock . Ideja je da kada nit želi pristup određenom resursu, dobije zaključavanje za taj resurs. Jednom kada nit zaključa određeni resurs, nijedna druga nit ne može mu pristupiti dok se zaključavanje ne otpusti. Kao rezultat toga, promjene resursa bit će atomske i izbjeći će se uvjeti utrke.
Brava je primitiv sinkronizacije na niskoj razini koji implementira modul __thread . U bilo kojem trenutku brava može biti u jednom od 2 stanja: zaključana ili otključana. Podržava dvije metode:
- steći()
Kada je stanje zaključavanja otključano, pozivanje metode придобиti () promijenit će stanje u zaključano i vratiti se. Međutim, ako je stanje zaključano, poziv za preuzimanje () blokira se dok neka druga nit ne pozove metodu release ().
- otpustiti ()
Metoda release () koristi se za postavljanje stanja na otključano, tj. Za otpuštanje brave. Može ga pozvati bilo koja nit, ne nužno ona koja je stekla bravu.
Evo primjera upotrebe brava u svojim aplikacijama. Zapalite svoj IDLE i upišite sljedeće:
import threadinglock = threading.Lock()def first_function():for i in range(5):lock.acquire()print ('lock acquired')print ('Executing the first funcion')lock.release()def second_function():for i in range(5):lock.acquire()print ('lock acquired')print ('Executing the second funcion')lock.release()if __name__=="__main__":thread_one = threading.Thread(target=first_function)thread_two = threading.Thread(target=second_function)thread_one.start()thread_two.start()thread_one.join()thread_two.join()
Sada pritisnite F5. Trebali biste vidjeti izlaz poput ovog:
OBJAŠNJENJE KODA
- Ovdje jednostavno stvarate novu bravu pozivanjem tvorničke funkcije threadading.Lock () . Interno Lock () vraća primjerak najučinkovitije konkretne klase Lock koju održava platforma.
- U prvom iskazu zaključavanje stječete pozivanjem metode accept (). Kada je zaključavanje odobreno, na konzolu ispisujete "zaključano stečeno" . Nakon što se sav kôd za koji želite da se nit izvede, završi izvršenje, otpuštate zaključavanje pozivanjem metode release ().
Teorija je u redu, ali kako znate da je brava stvarno funkcionirala? Ako pogledate izlaz, vidjet ćete da se svaki ispis ispisuje točno po jedan redak. Sjetimo se da su u ranijem primjeru izlazi iz ispisa bili slučajni jer je više niti istodobno pristupilo metodi print (). Ovdje se funkcija ispisa poziva tek nakon stjecanja zaključavanja. Dakle, izlazi se prikazuju jedan po jedan i red po red.
Osim zaključavanja, python podržava i neke druge mehanizme za upravljanje sinkronizacijom niti kao što je navedeno u nastavku:
- RLocks
- Semafori
- Uvjeti
- Događaji i
- Prepreke
Global Interpreter Lock (i kako se nositi s tim)
Prije ulaska u detalje python-ovog GIL-a, definirajmo nekoliko pojmova koji će biti korisni za razumijevanje predstojećeg odjeljka:
- Kôd vezan za CPU: ovo se odnosi na bilo koji dio koda koji će CPU izravno izvršiti.
- I / O vezani kôd: ovo može biti bilo koji kôd koji pristupa datotečnom sustavu putem OS-a
- CPython: referentna je implementacija Pythona i može se opisati kao tumač napisan na C i Python (programski jezik).
Što je GIL u Pythonu?
Global Interpreter Lock (GIL) u pythonu je blokada procesa ili mutex koji se koristi dok se radi s procesima. Osigurava da jedna nit istovremeno može pristupiti određenom resursu, a također sprječava upotrebu objekata i bajt kodova odjednom. To koristi programima s jednim navojem u povećanju izvedbe. GIL u pythonu vrlo je jednostavan i lagan za implementaciju.
Brava se može koristiti kako bi se osiguralo da samo jedna nit u određenom trenutku ima pristup određenom resursu.
Jedna od značajki Pythona je da koristi globalno zaključavanje svakog postupka tumača, što znači da svaki proces samog interpretatora pythona tretira kao resurs.
Na primjer, pretpostavimo da ste napisali python program koji koristi dvije niti za obavljanje i CPU i 'I / O' operacija. Kada izvršite ovaj program, događa se sljedeće:
- Python interpreter stvara novi proces i stvara niti
- Kad nit-1 počne raditi, prvo će nabaviti GIL i zaključati ga.
- Ako thread-2 želi izvršiti sada, morat će pričekati puštanje GIL-a čak i ako je slobodan drugi procesor.
- Sada, pretpostavimo da nit-1 čeka na I / O operaciju. Trenutno će otpustiti GIL, a nit-2 će ga steći.
- Nakon završetka I / O operacija, ako nit-1 želi izvršiti sada, morat će pričekati da GIL bude pušten pomoću niti-2.
Zbog toga samo jedna nit može pristupiti tumaču u bilo kojem trenutku, što znači da će postojati samo jedna nit koja izvršava python kôd u određenom trenutku.
To je u redu s jednojezgrenim procesorom jer bi za obradu niti koristio vremensko rezanje (vidi prvi odjeljak ovog vodiča). Međutim, u slučaju višejezgrenih procesora, funkcija vezana uz CPU koja se izvršava na više niti imat će značajan utjecaj na učinkovitost programa jer zapravo neće istodobno koristiti sve dostupne jezgre.
Zašto je bio potreban GIL?
CPython sakupljač smeća koristi učinkovitu tehniku upravljanja memorijom poznatu kao brojanje referenci. Evo kako to funkcionira: Svaki objekt u pythonu ima broj referenci, koji se povećava kada se dodijeli novom imenu varijable ili doda u spremnik (poput tupleva, popisa itd.). Isto tako, broj referenci smanjuje se kada referenca izlazi iz opsega ili kada se pozove del izraz. Kada broj referenci objekta dosegne 0, sakuplja se smeće i oslobađa se dodijeljena memorija.
Ali problem je u tome što je varijabla broja referenci sklona rasnim uvjetima kao i svaka druga globalna varijabla. Da bi riješili ovaj problem, programeri pythona odlučili su upotrijebiti globalnu bravu tumača. Druga je opcija bila dodavanje brave svakom objektu što bi rezultiralo zastojima i povećanim općim troškovima za pozive preuzimanja () i otpuštanja ().
Stoga je GIL značajno ograničenje za višenitne python programe koji izvode teške operacije vezane uz CPU (učinkovito ih čine jednonitnim). Ako u svojoj aplikaciji želite koristiti više jezgri procesora, umjesto toga upotrijebite modul za višeprocesorsku obradu .
Sažetak
- Python podržava 2 modula za multithreading:
- __thread modul: Pruža implementaciju navoja na niskoj razini i zastario je.
- thread thread modul : Pruža visoku razinu implementacije za multithreading i trenutni je standard.
- Da biste stvorili nit pomoću modula za navoja, morate učiniti sljedeće:
- Stvorite klasu koja proširuje klasu Thread .
- Zamijenite njegov konstruktor (__init__).
- Nadjačajte njegovu metodu run () .
- Stvorite objekt ove klase.
- Nit se može izvršiti pozivanjem metode start () .
- Metoda join () može se koristiti za blokiranje ostalih niti dok ova nit (ona na kojoj je pozvano join) ne završi izvršenje.
- Utrka se događa kada više niti istovremeno pristupa ili mijenja zajednički resurs.
- To se može izbjeći sinkroniziranjem niti.
- Python podržava 6 načina za sinkronizaciju niti:
- Brave
- RLocks
- Semafori
- Uvjeti
- Događaji i
- Prepreke
- Brave omogućuju samo određenom navoju koji je stekao bravu da uđe u kritični odjeljak.
- Brava ima 2 primarne metode:
- придобиti () : Postavlja stanje zaključavanja na zaključano. Ako se pozove na zaključani objekt, blokira se dok se resurs ne oslobodi.
- otpustiti () : Postavlja stanje brave na otključana i vraća. Ako se pozove na otključani objekt, vraća false.
- Globalno zaključavanje tumača je mehanizam pomoću kojeg se istovremeno može izvršiti samo 1 proces tumača CPython.
- Upotrijebljen je za olakšavanje funkcije brojanja referenci CPythonsovog sakupljača smeća.
- Da biste napravili Python aplikacije s teškim operacijama vezanim uz CPU, trebali biste koristiti modul za višeprocesorsku obradu.