Monisäikeinen sisään Python Esimerkki: Opi GIL sisään Python
⚡ Älykäs yhteenveto
Monisäikeinen sisään Python suorittaa useita säikeitä yhden prosessin sisällä, joten ne jakavat muistia ja toimivat samanaikaisesti. Säikeytysmoduuli luo ja hallitsee näitä säikeitä, kun taas globaali tulkintalukko rajoittaa todellista rinnakkaisuutta, mikä tekee tekniikasta parhaan vaihtoehdon syötteeseen/lähtöön sidotuille tehtäville.

Python ohjelmointikieli mahdollistaa moniprosessoinnin tai monisäikeisyyden käytön. Tässä opetusohjelmassa opit kirjoittamaan monisäikeisiä sovelluksia Python.
Mikä on säiettä?
Säie on rinnakkaisohjelmoinnin suoritusyksikkö. Monisäikeisyys on tekniikka, jonka avulla suoritin voi suorittaa useita yhden prosessin tehtäviä samanaikaisesti. Nämä säikeet voivat suorittaa toimintoja erikseen ja jakaa prosessiresurssejaan.
Mikä on prosessi?
Prosessi on pohjimmiltaan suoritettava ohjelma. Kun käynnistät sovelluksen tietokoneellasi (kuten selaimen tai tekstieditorin), käyttöjärjestelmä luo prosessiin.
Mikä on monisäikeistys Python?
Monisäikeinen sisään Python Ohjelmointi on tunnettu tekniikka, jossa useat prosessin säikeet jakavat datatilansa pääseimän kanssa, mikä tekee tiedon jakamisesta ja kommunikoinnista säikeiden sisällä helppoa ja tehokasta. Säikeet ovat kevyempiä kuin prosessit. Useita säikeitä voidaan suorittaa erikseen ja jakaa samalla prosessiresurssejaan. Monisäikeisyyden tarkoituksena on suorittaa useita tehtäviä ja funktioita samanaikaisesti.
Mitä on monikäsittely?
multiprocessing voit suorittaa useita toisiinsa liittymättömiä prosesseja samanaikaisesti. Nämä prosessit eivät jaa resurssejaan ja kommunikoi IPC:n kautta.
Python Monisäikeinen vs. moniprosessointi
Ymmärtääksesi prosesseja ja säikeitä, kuvittele seuraava tilanne: Tietokoneellasi oleva .exe-tiedosto on ohjelma. Kun avaat sen, käyttöjärjestelmä lataa sen muistiin ja suoritin suorittaa sen. Ohjelman käynnissä olevaa instanssia kutsutaan prosessiksi.
Jokaisella prosessilla on kaksi perustavanlaatuista osaa:
- Code
- Tiedot
Nyt prosessi voi sisältää yhden tai useamman aliosan nimeltä kierteet. Tämä riippuu käyttöjärjestelmän arkkitehtuurista. Voit ajatella säiettä prosessin osana, jonka käyttöjärjestelmä voi suorittaa erikseen.
Toisin sanoen se on käskyjen virta, jota käyttöjärjestelmä voi suorittaa itsenäisesti. Yhden prosessin säikeet jakavat prosessin tiedot ja ne on suunniteltu toimimaan yhdessä rinnakkaisuuden helpottamiseksi.
Miksi käyttää Multithreadingiä?
Monisäikeistyksen avulla voit jakaa sovelluksen useisiin alitehtäviin ja suorittaa nämä tehtävät samanaikaisesti. Jos käytät monisäikeistystä oikein, sovelluksesi nopeutta, suorituskykyä ja renderöintiä voidaan parantaa.
Python säikeistystekniikkaa
Python tukee sekä moniajoprosessointi- että monisäikeistysrakenteita. Tässä opetusohjelmassa keskitytään pääasiassa toteuttamiseen monisäikeinen sovellusten kanssa PythonSäikeiden käsittelyyn on kaksi päämoduulia, joita voidaan käyttää Python:
- kierre moduuli ja
- ketjuttaminen moduuli
Kuitenkin vuonna Python, on olemassa myös niin sanottu globaali tulkintalukko (GIL). Se ei tarjoa paljoa suorituskyvyn parannusta ja voi jopa vähentää joidenkin monisäikeisten sovellusten suorituskykyä. Opit siitä kaiken tämän opetusohjelman tulevissa osissa.
Thread- ja Threading-moduulit
Kaksi moduulia, joista opit tässä opetusohjelmassa, ovat kierremoduuli ja kierteitysmoduuli.
Lankamoduuli on kuitenkin vanhentunut pitkään. alkaen Python 3, se on merkitty vanhentuneeksi ja siihen pääsee vain nimellä _kierre taaksepäin yhteensopivuuden vuoksi.
Sinun pitäisi käyttää korkeampaa tasoa ketjuttaminen moduuli sovelluksille, jotka aiot ottaa käyttöön. Säikeiden moduulia on käsitelty tässä vain koulutustarkoituksiin.
Lankamoduuli
Syntaksi uuden säikeen luomiseksi tällä moduulilla on seuraava:
thread.start_new_thread(function_name, arguments)
Selvä, nyt olet käynyt läpi perusteorian aloittaaksesi koodauksen. Joten, avaa omasi IDLE tai muistilehtiö ja kirjoita seuraava:
import time import _thread def thread_test(name, wait): i = 0 while i <= 3: time.sleep(wait) print("Running %s\n" %name) i = i + 1 print("%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))
Tallenna tiedosto ja paina F5 käynnistääksesi ohjelman. Jos kaikki tehtiin oikein, sinun pitäisi nähdä tämä tulos:
Opit lisää kilpailuolosuhteista ja niiden käsittelystä seuraavissa osioissa.
KOODIN SELITYS
- Nämä lauseet tuovat aika- ja säiemoduulin, joita käytetään funktion suorituksen ja viiveen käsittelyyn. Python kierteet.
- Tässä olet määrittänyt funktion nimeltä thread_test, jota kutsutaan nimellä aloita_uusi_ketju metodi. Funktio suorittaa while-silmukan neljän iteraation ajan ja tulostaa sitä kutsuneen säikeen nimen. Kun iteraatio on valmis, se tulostaa viestin, jossa ilmoitetaan, että säikeen suoritus on päättynyt.
- Tämä on ohjelmasi pääosa. Täällä voit vain soittaa aloita_uusi_ketju menetelmän kanssa thread_test funktiota argumenttina. Tämä luo uuden säikeen argumenttina antamallesi funktiolle ja aloittaa sen suorittamisen. Huomaa, että voit korvata tämän (thread_test) millä tahansa muulla funktiolla, jonka haluat suorittaa säikeenä.
Langoitusmoduuli
Tämä moduuli on säikeistämisen korkean tason toteutus Python ja tosiasiallinen standardi monisäikeisten sovellusten hallintaan. Se tarjoaa laajan valikoiman ominaisuuksia verrattuna säiemoduuliin.
Threading-moduulin rakenne
Tässä on luettelo hyödyllisistä tässä moduulissa määritellyistä toiminnoista:
| Toiminnon nimi | Tuotetiedot |
|---|---|
| ActiveCount() | Palauttaa määrän Kierre esineitä, jotka ovat edelleen elossa. |
| nykyinen säie() | Palauttaa Thread-luokan nykyisen objektin. |
| luetella() | Luetteloi kaikki aktiiviset säikeen objektit. |
| isDaemon() | Palauttaa tosi, jos säie on demoni. |
| on elossa() | Palauttaa tosi, jos lanka on vielä elossa. |
| Säieluokan menetelmät | |
| alkaa() | Käynnistää säikeen toiminnan. Sitä on kutsuttava vain kerran jokaiselle säikeelle, koska se aiheuttaa ajonaikaisen virheen, jos sitä kutsutaan useita kertoja. |
| juosta() | Tämä menetelmä ilmaisee säikeen toimintaa, ja sen voi ohittaa luokka, joka laajentaa säikeen luokkaa. |
| liittyä seuraan() | Se estää muun koodin suorittamisen, kunnes säie, jossa join()-menetelmää kutsuttiin, lopetetaan. |
Taustatarina: The Thread Class
Ennen kuin aloitat monisäikeisten ohjelmien koodaamisen säikeistusmoduulin avulla, on tärkeää ymmärtää Thread-luokka. Thread-luokka on ensisijainen luokka, joka määrittelee säikeen mallineen ja toiminnot. Python.
Yleisin tapa luoda monisäikeinen Python sovelluksen tehtävänä on määrittää luokka, joka laajentaa Thread-luokkaa ja korvaa sen run()-metodin.
Yhteenvetona Thread-luokka merkitsee koodisekvenssiä, joka suoritetaan erillisenä kierre hallintaan.
Joten, kun kirjoitat monisäikeistä sovellusta, teet seuraavaa:
- määrittele luokka, joka laajentaa Thread-luokkaa
- Ohita __init__ rakentaja
- Ohita juosta() menetelmä
Kun lankaobjekti on tehty, alkaa() menetelmää voidaan käyttää tämän toiminnan suorittamisen aloittamiseen, ja liittyä seuraan() -menetelmää voidaan käyttää estämään kaikki muu koodi, kunnes nykyinen toiminta on päättynyt.
Yritetään nyt käyttää säikeistusmoduulia edellisen esimerkkisi toteuttamiseen. Käynnistä uudelleen IDLE ja kirjoita seuraava:
import time import threading class threadtester (threading.Thread): def __init__(self, id, name, i): threading.Thread.__init__(self) self.id = id self.name = name self.i = i def 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 - 1 if __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()
Tämä on tulos, kun suoritat yllä olevan koodin:
KOODIN SELITYS
- Tämä osa on sama kuin edellinen esimerkkimme. Tässä tuodaan aika- ja säikemoduuli, joita käytetään käsittelemään funktion suoritusta ja viiveitä. Python kierteet.
- Tässä bitissä luot luokan nimeltä threadtester, joka perii tai laajentaa Kierre säikeiden luontimoduulin luokka. Tämä on yksi yleisimmistä tavoista luoda säikeitä PythonSinun tulisi kuitenkin korvata vain konstruktori ja juosta() menetelmää sovelluksessasi. Kuten yllä olevasta koodinäytteestä näet, __init__ menetelmä (konstruktori) on ohitettu. Samoin olet myös ohittanut juosta() menetelmä. Se sisältää koodin, jonka haluat suorittaa säikeen sisällä. Tässä esimerkissä olet kutsunut thread_test()-funktiota.
- Tämä on thread_test()-metodi, joka ottaa arvon i argumenttina, vähentää sitä yhdellä jokaisella iteraatiolla ja käy läpi lopun koodin, kunnes i:stä tulee 0. Jokaisella iteraatiolla se tulostaa parhaillaan suoritettavan säikeen nimen ja odottaa sekuntia (mikä myös otetaan argumenttina).
- thread1 = threadtester(1, "First Thread", 1) Tässä luomme säiettä ja välitämme kolme parametria, jotka määritimme __init__:ssa. Ensimmäinen parametri on säikeen id, toinen parametri on säikeen nimi ja kolmas parametri on laskuri, joka määrittää kuinka monta kertaa while-silmukan tulee suorittaa.
- thread2.start() start-metodia käytetään säikeen suorituksen aloittamiseen. Sisäisesti start()-funktio kutsuu luokkasi run()-metodia.
- thread3.join() Join()-metodi estää muun koodin suorittamisen ja odottaa, kunnes säie, jossa sitä kutsuttiin, päättyy.
Kuten jo tiedät, samassa prosessissa olevilla säikeillä on pääsy kyseisen prosessin muistiin ja dataan. Tämän seurauksena, jos useampi kuin yksi säie yrittää muuttaa tai käyttää dataa samanaikaisesti, virheitä voi hiipiä sisään.
Seuraavassa osiossa näet erilaisia ongelmia, joita voi ilmetä, kun säikeet käyttävät dataa ja kriittistä osiota tarkistamatta olemassa olevia käyttöoikeustapahtumia.
Umpikujat ja kilpailuolosuhteet
Ennen kuin perehdymme lukkiutumiin ja kilpailutilanteisiin, on hyödyllistä ymmärtää muutamia rinnakkaisohjelmointiin liittyviä perusmääritelmiä:
- Kriittinen jakso: Se on koodinpätkä, joka käyttää tai muokkaa jaettuja muuttujia ja se on suoritettava atomitason tapahtumana.
- Kontekstin vaihto: Se on prosessi, jota suoritin noudattaa tallentaakseen säikeen tilan ennen siirtymistä tehtävästä toiseen, jotta sitä voidaan jatkaa samasta pisteestä myöhemmin.
Umpikujasta
Umpikujasta ovat pelätyimpiä ongelmia, joita kehittäjät kohtaavat kirjoittaessaan rinnakkaisia/monisäikeisiä sovelluksia PythonParas tapa ymmärtää lukkiutumia on käyttää klassista tietojenkäsittelytieteen esimerkkiongelmaa, joka tunnetaan nimellä Ruokaa Philosophers Ongelma.
Ruokailufilosofien ongelmanratkaisu on seuraava:
Viisi filosofia istuu pyöreän pöydän ääressä, jossa on viisi lautasta spagettia (eräänlaista pastaa) ja viisi haarukkaa, kuten kaaviossa näkyy.
Ruokaa Philosophers Ongelma
Filosofin täytyy milloin tahansa joko syödä tai ajatella.
Lisäksi filosofin on otettava kaksi vierekkäistä haarukkaa (eli vasen ja oikea haarukka), ennen kuin hän voi syödä spagettia. Umpikujaongelma syntyy, kun kaikki viisi filosofia poimivat oikeat haarukat samanaikaisesti.
Koska jokaisella filosofeilla on yksi haarukka, he kaikki odottavat, että muut laskevat haarukkansa alas. Tämän seurauksena kukaan heistä ei voi syödä spagettia.
Vastaavasti samanaikaisessa järjestelmässä lukkiutuminen tapahtuu, kun eri säikeet tai prosessit (filosofit) yrittävät hankkia jaetut järjestelmäresurssit (haarukat) samanaikaisesti. Tämän seurauksena yksikään prosesseista ei saa mahdollisuutta suorittaa, koska ne odottavat toista resurssia, joka on jonkin muun prosessin hallussa.
Kilpailun ehdot
Kilpailutilanne on ohjelman ei-toivottu tila, joka syntyy, kun järjestelmä suorittaa kaksi tai useampia operaatioita samanaikaisesti. Tarkastellaan esimerkiksi tätä yksinkertaista for-silmukkaa:
i=0; # a global variable for x in range(100): print(i) i+=1;
Jos luot n Jos koodia suoritetaan samanaikaisesti useamman säikeen vuoksi, et voi määrittää i:n arvoa (jonka säikeet jakavat), kun ohjelman suoritus on päättynyt. Tämä johtuu siitä, että todellisessa monisäikeisessä ympäristössä säikeet voivat mennä päällekkäin, ja säikeen hakema ja muokkaama i:n arvo voi muuttua, kun jokin toinen säie käyttää sitä.
Nämä ovat kaksi pääasiallista ongelmaluokkaa, joita voi esiintyä monisäikeisessä tai hajautetussa Python sovellus. Seuraavassa osiossa opit ratkaisemaan tämän ongelman synkronoimalla säikeitä.
Synchronisoivat langat
Pujotusmoduuli tarjoaa kilpailuolosuhteiden, umpikujan ja muiden lankaan perustuvien ongelmien ratkaisemiseksi Lukita esine. Ajatuksena on, että kun säie haluaa pääsyn tiettyyn resurssiin, se hankkii lukon tälle resurssille. Kun lanka lukitsee tietyn resurssin, mikään muu säie ei voi käyttää sitä ennen kuin lukko vapautetaan. Tämän seurauksena muutokset resurssissa ovat ydinaseita ja kilpailuolosuhteet vältetään.
Lukko on matalan tason synkronointiprimitiivi, jonka on toteuttanut _kierre moduuli. Lukko voi olla millä tahansa hetkellä yhdessä kahdesta tilasta: lukittu or lukitsematon. Se tukee kahta menetelmää:
- hankkia(): Kun lukitustila on avattu, acquire()-metodin kutsuminen muuttaa tilan lukituksi ja palauttaa arvon. Jos tila on kuitenkin lukittu, acquire()-metodin kutsu estetään, kunnes jokin toinen säie kutsuu release()-metodia.
- vapauta(): Release()-menetelmää käytetään asettamaan tila lukitsemattomaksi, eli vapauttamaan lukko. Sitä voi kutsua mikä tahansa lanka, ei välttämättä se, joka hankki lukon.
Tässä on esimerkki lukkojen käytöstä sovelluksissasi. Käynnistä oma IDLE ja kirjoita seuraava:
import threading lock = 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()
Paina nyt F5. Sinun pitäisi nähdä seuraavanlainen tulos:
KOODIN SELITYS
- Tässä luot vain uuden lukon soittamalla numeroon ketjutus.Lukko() tehdastoiminto. Sisäisesti Lock() palauttaa tehokkaimman konkreettisen Lock-luokan esiintymän, jota alusta ylläpitää.
- Ensimmäisessä lauseessa hankit lukon kutsumalla hanki()-menetelmää. Kun lukko on myönnetty, tulostat "lukko hankittu" konsoliin. Kun kaikki koodi, jonka haluat ketjun suorittavan, on suoritettu, vapautat lukon kutsumalla release()-menetelmää.
Teoria on ihan hyvä, mutta mistä tiedät, että lukko todella toimi? Jos katsot tulostetta, huomaat, että jokainen print-lauseke tulostaa täsmälleen yhden rivin kerrallaan. Muista, että aiemmassa esimerkissä print-funktion tulosteet olivat sattumanvaraisia, koska useat säikeet käyttivät print()-metodia samanaikaisesti. Tässä print-funktiota kutsutaan vasta lukon hankkimisen jälkeen. Joten tulosteet näytetään yksi kerrallaan ja rivi riviltä.
Lukkojen lisäksi, Python tukee myös joitakin muita mekanismeja säikeiden synkronoinnin käsittelemiseksi, kuten alla on lueteltu:
- RLukot
- Semaphores
- olosuhteet
- Tapahtumat ja
- Esteet
Global Interpreter Lock (ja kuinka käsitellä sitä)
Ennen kuin menet yksityiskohtiin PythonGIL:n osalta määritellään muutamia termejä, joista on hyötyä seuraavan osan ymmärtämisessä:
- CPU-sidottu koodi: tämä viittaa mihin tahansa koodinpätkään, jonka CPU suorittaa suoraan.
- I/O-sidottu koodi: tämä voi olla mitä tahansa koodia, joka käyttää tiedostojärjestelmää käyttöjärjestelmän kautta.
- CPython: se on viite täytäntöönpano of Python ja sitä voidaan kuvata C- ja kielellä kirjoitettuna tulkkina Python (ohjelmointikieli).
Mitä GIL sisältää Python?
Global Interpreter Lock (GIL) in Python on prosessilukko tai mutex, jota käytetään prosessien käsittelyyn. Se varmistaa, että yksi säie voi käyttää tiettyä resurssia kerrallaan, ja se estää myös objektien ja tavukoodien käytön samanaikaisesti. Tämä hyödyttää yksisäikeisiä ohjelmia suorituskyvyn kasvun kautta. GIL Python on erittäin yksinkertainen ja helppo toteuttaa.
Lukkoa voidaan käyttää varmistamaan, että vain yhdellä säikeellä on pääsy tiettyyn resurssiin tiettynä aikana.
Yksi ominaisuuksista Python on, että se käyttää globaalia lukitusta jokaiselle tulkkiprosessille, mikä tarkoittaa, että jokainen prosessi käsittelee Python tulkki itse resurssina.
Oletetaan esimerkiksi, että olet kirjoittanut Python ohjelma, joka käyttää kahta säiettä sekä suorittimen että I/O-toimintojen suorittamiseen. Kun suoritat tämän ohjelman, tapahtuu seuraavaa:
- Python tulkki luo uuden prosessin ja käynnistää säikeet.
- Kun säie-1 käynnistyy, se hankkii ensin GIL:n ja lukitsee sen.
- Jos säie-2 haluaa suorittaa nyt, sen on odotettava GIL:n julkaisua, vaikka toinen prosessori olisi vapaa.
- Oletetaan nyt, että säie-1 odottaa I/O-toimintoa. Tällä hetkellä se vapauttaa GIL:n ja säie-2 hankkii sen.
- I/O-operaatioiden suorittamisen jälkeen, jos säie-1 haluaa suorittaa nyt, sen on jälleen odotettava GIL:n vapauttamista säiettä-2:lla.
Tästä johtuen vain yksi säie voi käyttää tulkkia kerrallaan, mikä tarkoittaa, että vain yksi säie on suorittamassa sitä. Python koodi tiettynä ajankohtana.
Tämä on ihan ok yksiydinprosessorissa, koska se käyttäisi aikaviipalointia (katso tämän tutoriaalin ensimmäinen osio) säikeiden käsittelyyn. Moniydinprosessoreiden tapauksessa useissa säikeissä suoritettava suoritinkohtainen funktio vaikuttaa kuitenkin huomattavasti ohjelman tehokkuuteen, koska se ei todellisuudessa käytä kaikkia käytettävissä olevia ytimiä samanaikaisesti.
Miksi GIL:ää tarvittiin?
CPython roskienkerääjä käyttää tehokasta muistinhallintatekniikkaa, joka tunnetaan nimellä viitelaskenta. Näin se toimii: Jokainen objekti Python lla on viitemäärä, jota kasvatetaan, kun se liitetään uuteen muuttujan nimeen tai lisätään säiliöön (kuten tupleihin, listoihin jne.). Vastaavasti viitemäärää pienennetään, kun viite poistuu soveltamisalasta tai kun del-lausetta kutsutaan. Kun objektin viitemäärä saavuttaa arvon 0, se kerätään roskiin ja varattu muisti vapautetaan.
Mutta ongelmana on, että viitemäärämuuttuja on altis kilpailuolosuhteille kuten mikä tahansa muu globaali muuttuja. Tämän ongelman ratkaisemiseksi kehittäjät Python päätti käyttää globaalia tulkintalukkoa. Toinen vaihtoehto oli lisätä lukko jokaiseen objektiin, mikä olisi johtanut lukkiutumiin ja lisännyt acquire()- ja release()-kutsujen aiheuttamaa työmäärää.
Siksi GIL on merkittävä rajoitus monisäikeisille menetelmille. Python ohjelmia, jotka suorittavat raskaita suorittimen kuormitusta vaativia toimintoja (mikä tekee niistä käytännössä yksisäikeisiä). Jos haluat hyödyntää useita suorittimen ytimiä sovelluksessasi, käytä monikäsittely moduuli sen sijaan.








