Püütoni programmeerimiskeel võimaldab teil kasutada mitmeprotsessilist või mitmikeermelist töötlemist. Selles õpetuses saate teada, kuidas kirjutada Pythonis mitmikeermelisi rakendusi.
Mis on niit?
Lõng on samaaegse programmeerimise üksus. Mitme keermestamine on tehnika, mis võimaldab protsessoril täita korraga ühe protsessi paljusid ülesandeid. Neid lõime saab oma protsessiressursse jagades käivitada eraldi.
Mis on protsess?
Protsess on põhimõtteliselt käivitatav programm. Kui käivitate arvutis rakenduse (nt brauser või tekstiredaktor), loob operatsioonisüsteem protsessi.
Mis on Pythonis mitmikeermeline lugemine?
Mitme keermestamine Pythoni programmeerimisel on tuntud tehnika, mille käigus protsessi mitu lõime jagavad oma andmeruumi peaniidiga, mis muudab teabe jagamise ja niitides suhtlemise lihtsaks ja tõhusaks. Lõngad on protsessidest kergemad. Protsessiressursse jagades võib mitu lõime käivitada eraldi. Mitme lõime eesmärk on käivitada korraga mitu ülesannet ja funktsiooni lahtrit.
Mis on mitmeprotsessiline töötlemine?
Mitmeprotsessiline töötamine võimaldab teil korraga käivitada mitu omavahel mitteseotud protsessi. Need protsessid ei jaga oma ressursse ega suhtle IPC kaudu.
Pythoni mitmikeermeline vs mitmeprotsessiline
Protsesside ja lõimete mõistmiseks kaaluge seda stsenaariumi: teie arvutis olev .exe-fail on programm. Selle avamisel laadib OS selle mällu ja protsessor täidab selle. Praegu töötava programmi eksemplari nimetatakse protsessiks.
Igal protsessil on 2 põhikomponenti:
- Kood
- Andmed
Nüüd võib protsess sisaldada ühte või mitut alamosa, mida nimetatakse lõimedeks. See sõltub OS-i arhitektuurist. Võite mõelda lõimest kui protsessi osast, mida operatsioonisüsteem saab eraldi käivitada.
Teisisõnu, see on käskude voog, mida OS saab iseseisvalt käivitada. Ühes protsessis olevad lõimed jagavad selle protsessi andmeid ja on mõeldud paralleelsuse hõlbustamiseks koos töötama.
Selles õpetuses saate teada,
- Mis on niit?
- Mis on protsess?
- Mis on mitmikeermeline?
- Mis on mitmeprotsessiline töötlemine?
- Pythoni mitmikeermeline vs mitmeprotsessiline
- Miks kasutada mitmikeermelist?
- Pythoni multiThreading
- Keerme ja lõime moodulid
- Keermemoodul
- Keermestamise moodul
- Ummikud ja võistlustingimused
- Lõngade sünkroonimine
- Mis on GIL?
- Miks oli vaja GIL-i?
Miks kasutada mitmikeermelist?
Mitme lõime abil saate jaotada rakenduse mitmeks alaülesandeks ja käivitada need toimingud üheaegselt. Kui kasutate mitmikeermelist õigesti, saab teie rakenduse kiirust, jõudlust ja renderdamist parandada.
Pythoni multiThreading
Python toetab konstruktsioone nii mitmeprotsessiliseks kui ka mitmekeermeliseks töötlemiseks. Selles õpetuses keskendute peamiselt mitmekeermeliste rakenduste juurutamisele pythoniga . Pythoni lõimede käsitsemiseks on kaks peamist moodulit:
- Niidi moodul ja
- Väliskeermestamiseks moodul
Kuid pythonis on ka midagi, mida nimetatakse globaalseks tõlkide lukuks (GIL). See ei võimalda palju jõudlust tõsta ja võib isegi vähendada mõne mitmikeermelise rakenduse jõudlust. Selle kohta saate teada selle õpetuse järgmistest osadest.
Keerme ja lõime moodulid
Kaks moodulit, mida selles õpetuses saate teada, on lõimemoodul ja lõimemoodul .
Kuid lõimemoodul on pikka aega iganenud. Alates Python 3-st on see määratud vananenuks ja sellele on tagurpidi ühilduvuse huvides juurdepääsetav ainult kui __thread .
Rakenduste jaoks, mida kavatsete juurutada, peaksite kasutama kõrgema taseme keermestusmoodulit . Niitmoodulit on siin käsitletud ainult hariduslikel eesmärkidel.
Keermemoodul
Selle mooduli abil uue lõime loomiseks on süntaks järgmine:
thread.start_new_thread(function_name, arguments)
Hästi, nüüd olete kodeerimise alustamiseks käsitlenud põhiteooriat. Niisiis, avage oma IDLE või märkmik ja tippige järgmine:
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))
Salvestage fail ja vajutage programmi käivitamiseks klahvi F5. Kui kõik tehti õigesti, peaksite nägema järgmist väljundit:
Lisateavet võistlustingimuste ja nende käsitlemise kohta saate eelseisvatest lõikudest
KOODI SELETUS
- Need avaldused impordivad aja- ja lõimemoodulit, mida kasutatakse Pythoni lõimede käivitamise ja viivitamise käsitlemiseks.
- Siin on määratletud funktsioon nimega thread_test, mida nimetatakse mida start_new_thread meetod. Funktsioon käivitab nelja iteratsiooni ajalise loopi ja prindib selle kutsunud lõime nime. Kui iteratsioon on lõpule jõudnud, prindib see teate, milles öeldakse, et lõim on lõpetatud.
- See on teie programmi peamine osa. Siin lihtsalt helistada start_new_thread meetod koos thread_test funktsiooni argumendina.
See loob argumendina edastatavale funktsioonile uue lõime ja hakkab seda käivitama. Pange tähele, et saate selle (lõime _ test) asendada mis tahes muu funktsiooniga, mida soovite lõimina käivitada.
Keermestamise moodul
See moodul on püthoni keermestamise kõrgetasemeline rakendamine ja de facto standard mitmekeermeliste rakenduste haldamiseks. See pakub lõimemooduliga võrreldes paljusid funktsioone.

Siin on loetelu mõnest selles moodulis määratletud kasulikust funktsioonist:
Funktsiooni nimi | Kirjeldus |
activeCount () | Tagastab veel elus olevate lõimeobjektide arvu |
currentThread () | Tagastab klassi Thread praeguse objekti. |
loendama () | Loetleb kõik aktiivsed lõimeobjektid. |
isDaemon () | Tagastab tõene, kui lõim on deemon. |
on elus() | Tagastab tõene, kui lõime on veel elus. |
Keermeklassi meetodid | |
algus () | Alustab lõime tegevust. Iga lõime jaoks tuleb seda kutsuda ainult üks kord, kuna see kutsub mitu korda käitamisvea. |
jooksma () | See meetod tähistab lõime aktiivsust ja selle saab alistada klass, mis laiendab lõime klassi. |
liitu () | See blokeerib muu koodi täitmise seni, kuni lõim, millele meetodit join () kutsuti, lõpetatakse. |
Lugu: lõimeklass
Enne mitmikeermeliste programmide kodeerimise alustamist keermestusmooduli abil on ülioluline mõista lõime klassi. Niidiklass on peamine klass, mis määratleb malli ja lõime toimingud pythonis.
Kõige tavalisem viis mitmekeermelise pythoni rakenduse loomiseks on deklareerida klass, mis laiendab lõime klassi ja alistab selle run () meetodi.
Thread klassi kokkuvõtvalt tähendab koodi järjestuse, mis töötab eraldi lõime kontrolli.
Nii et mitmikeermelise rakenduse kirjutamisel toimite järgmiselt.
- määrake klass, mis laiendab lõime klassi
- Alistamine __init__ ehitaja
- Alista run () meetod
Kui lõimeobjekt on tehtud, saab selle tegevuse käivitamiseks kasutada meetodit start () ja meetodit join () saab blokeerida kõik muud koodid kuni praeguse tegevuse lõpuni.
Proovime nüüd oma eelmise näite rakendamiseks kasutada keermestusmoodulit. Jällegi, käivitage oma IDLE ja tippige järgmine:
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()
See on väljund, kui täidate ülaltoodud koodi:
KOODI SELETUS
- See osa on sama mis meie eelmine näide. Siit impordite aja- ja lõimemooduli, mida kasutatakse Pythoni lõimede käivitamise ja viivituste käsitlemiseks.
- Selles bitis loote klassi nimega threadtester, mis pärib või laiendab keermestusmooduli lõime klassi. See on üks levinumaid viise lõime loomiseks pythonis. Siiski peaksite rakenduses ainult konstruktori ja run () meetodi alistama. Nagu ülaltoodud koodinäidisest näha, on meetod __init__ (konstruktor) alistatud .
Samamoodi olete alistanud ka run () meetodi. See sisaldab koodi, mille soovite lõime sees käivitada. Selles näites olete kutsunud funktsiooni thread_test ().
- See on meetod thread_test (), mis võtab argumendina i väärtuse , vähendab seda igal kordusel 1 võrra ja silmustab ülejäänud koodi läbi, kuni i saab väärtuseks 0. Igas iteratsioonis prindib see parajasti käivitatava lõime nime ja magab ootussekundeid (mida võetakse ka argumendina).
- thread1 = threadtester (1, "Esimene lõime", 1)
Siin loome lõime ja edastame kolm parameetrit, mille deklareerisime __init__. Esimene parameeter on lõime ID, teine parameeter on lõime nimi ja kolmas parameeter on loendur, mis määrab mitu korda while-tsükkel peaks töötama.
- thread2.start ()
Stardimeetodit kasutatakse lõime käivitamise alustamiseks. Sisemiselt kutsub funktsioon start () teie klassi run () meetodi.
- thread3.joine ()
Join () meetod blokeerib muu koodi käivitamise ja ootab, kuni lõim, millel seda nimetati, lõpeb.
Nagu te juba teate, on samas protsessis olevatel lõimedel juurdepääs selle protsessi mälule ja andmetele. Selle tagajärjel, kui rohkem kui üks lõime üritab andmeid korraga muuta või neile juurde pääseda, võivad vead sisse hiilida.
Järgmises jaotises näete erinevaid komplikatsioone, mis võivad ilmneda, kui niidid pääsevad juurde andmetele ja kriitilise sektsiooni olemasolevaid juurdepääsutehinguid kontrollimata.
Ummikud ja võistlustingimused
Enne ummikute ja võistlustingimuste tundmaõppimist on kasulik mõista mõnda põhidefinitsiooni, mis on seotud samaaegse programmeerimisega:
- Kriitiline osa
See on koodilõik, mis pääseb ligi või muudab jagatud muutujaid ja tuleb läbi viia aatomitehinguna.
- Kontekstlüliti
See on protsess, mida CPU järgib lõime oleku salvestamiseks enne ühelt ülesandelt teisele üleminekut, et seda saaks hiljem samast punktist jätkata.
Ummikud
Ummikud on kõige kardetavam probleem, millega arendajad silmitsi seisavad, kui kirjutate samaaegseid / mitmikeermelisi rakendusi pythonis. Parim viis ummikute mõistmiseks on klassikalise arvutiteaduse näidisprobleemi kasutamine, mida nimetatakse söögifilosoofide probleemiks.
Söögifilosoofide probleemipüstitus on järgmine:
Viis filosoofi istuvad ümmargusel laual, millel on viis plaati spagette (pastatüüp) ja viis kahvlit, nagu joonisel näidatud.

Igal ajahetkel peab filosoof kas sööma või mõtlema.
Pealegi peab filosoof enne spagetite söömist võtma kaks tema kõrval asuvat kahvlit (st vasakut ja paremat kahvlit). Ummikuprobleem tekib siis, kui kõik viis filosoofi võtavad korraga oma parema kahvli.
Kuna igal filosoofil on üks kahvel, ootavad nad kõik, kuni teised kahvli maha panevad. Seetõttu ei saa ükski neist spagette süüa.
Samamoodi tekib samaaegses süsteemis ummikseis siis, kui erinevad lõimed või protsessid (filosoofid) üritavad ühiskasutusse antud süsteemi ressursse (kahvleid) korraga omandada. Selle tulemusena ei saa ükski protsessidest võimalust teostada, kuna nad ootavad mõnda muud ressurssi, mida hoiab mõni muu protsess.
Võistlustingimused
Võistlustingimus on programmi soovimatu olek, mis tekib siis, kui süsteem sooritab korraga kahte või enamat toimingut. Näiteks pidage seda loopi jaoks lihtsaks:
i=0; # a global variablefor x in range(100):print(i)i+=1;
Kui loote n arvu lõime, mis seda koodi korraga käitavad, ei saa i täitmise lõpetamisel i väärtust (mida lõimed jagavad) kindlaks määrata. Selle põhjuseks on asjaolu, et reaalses mitmekeermelises keskkonnas võivad lõimed kattuda ja i väärtus, mille niit otsis ja muutis, võib vahel muutuda, kui mõni teine lõim sellele juurde pääseb.
Need on kaks peamist probleemiklassid, mis võivad ilmneda mitmikeermelises või hajutatud pythoni rakenduses. Järgmises jaotises saate teada, kuidas sellest probleemist lõimede sünkroonimise abil üle saada.
Lõngade sünkroonimine
Võistlustingimuste, ummikute ja muude lõimapõhiste probleemide lahendamiseks pakub keermestusmoodul objekti Lock . Idee on selles, et kui lõim soovib juurdepääsu konkreetsele ressursile, omandab ta sellele ressursile luku. Kui niit on konkreetse ressursi lukustanud, ei pääse ükski teine niit sellele enne, kui lukk vabastatakse. Selle tulemusena on ressursi muudatused aatomi ja võistlustingimused välditakse.
Lukk on madalama taseme sünkroonimisprimitiiv, mille rakendab moodul __thread . Igal ajahetkel võib lukk olla ühes kahest olekust: lukustatud või lukustamata. See toetab kahte meetodit:
- omandama ()
Kui lukustusolek on lukustamata, muudab meetod omandada () helistades oleku lukustatuks ja naasmiseks. Kui aga olek on lukus, blokeeritakse hankimiskõne () seni, kuni mõni muu lõime kutsub välja meetodi release ().
- vabastama ()
Meetodit release () kasutatakse oleku avamiseks lukustamiseks, st luku vabastamiseks. Seda saab nimetada mis tahes lõimega, mitte tingimata sellega, mis luku omandas.
Siin on näide lukkude kasutamisest oma rakendustes. Käivitage oma IDLE ja tippige järgmine:
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()
Nüüd vajuta F5. Peaksite nägema sellist väljundit:
KOODI SELETUS
- Siin loote lihtsalt uue luku, helistades tehase funktsiooni threading.Lock () . Sisemiselt tagastab Lock () platvormi hooldatava kõige tõhusama betoonluku klassi eksemplari.
- Esimeses lauses omandate luku, helistades meetodile omandamine (). Kui lukk on antud, prindite konsooli "omandatud lukk" . Kui kogu kood, mille soovite lõime käivitada, on lõpetatud, vabastate luku, kutsudes meetodi release ().
Teooria on hea, aga kuidas sa tead, et lukk tõesti töötas? Kui vaatate väljundit, näete, et iga trükiarvutus trükib täpselt ühte rida korraga. Tuletame meelde, et ühes varasemas näites on printide väljundid juhuslikud, kuna printimise () meetodile pääsesid korraga juurde mitu lõime. Siin kutsutakse printimisfunktsiooni alles pärast luku omandamist. Seega kuvatakse väljundid ükshaaval ja rea kaupa.
Peale lukkude toetab python ka mõningaid muid mehhanisme lõime sünkroonimise käsitlemiseks, nagu on loetletud allpool:
- R-lukud
- Semafoorid
- Tingimused
- Sündmused ja
- Tõkked
Globaalne tõlgi lukustus (ja kuidas sellega toime tulla)
Enne pythoni GIL-i üksikasjadega tutvumist määratleme mõned terminid, mis on eelseisva jaotise mõistmiseks kasulikud:
- Protsessoriga seotud kood: see viitab mis tahes koodile, mille protsessor otse täidab.
- Sisend- / väljundiga seotud kood: see võib olla mis tahes kood, mis pääseb OS-i kaudu failisüsteemi juurde
- CPython: see on viide rakendamise Python ja võib kirjeldada kui tõlk kirjutatud C ja Python (programmeerimiskeel).
Mis on Pythonis GIL?
Püthoni üldine tõlkide lukustus (GIL) on protsesside lukustamine või muteks, mida kasutatakse protsesside käsitlemisel. See tagab, et üks lõim pääseb korraga juurde konkreetsele ressursile, samuti takistab see korraga objektide ja baidekoodide kasutamist. See on üheahelaliste programmide jõudluse suurendamisel kasulik. GIL Pythonis on väga lihtne ja hõlpsasti rakendatav.
Luku abil saab veenduda, et kindlal ressursil on teatud ajal juurdepääs ainult ühel lõimel.
Pythoni üks omadusi on see, et ta kasutab igas tõlkeprotsessis globaalset lukustust, mis tähendab, et iga protsess käsitleb pythoni tõlki ennast ressursina.
Oletame näiteks, et olete kirjutanud pythoni programmi, mis kasutab kahte lõime nii protsessori kui ka sisendi / väljundi toimingute tegemiseks. Selle programmi käivitamisel juhtub nii:
- Püütonitõlk loob uue protsessi ja koob niidid
- Kui lõime-1 käivitamine algab, omandab see kõigepealt GIL-i ja lukustab selle.
- Kui thread-2 soovib kohe käivitada, peab ta ootama GIL-i vabastamist, isegi kui mõni teine protsessor on vaba.
- Oletame, et niit-1 ootab sisend- / väljundoperatsiooni. Sel ajal vabastab see GIL-i ja lõime-2 omandab selle.
- Kui sisend / väljundoperaatorid on lõpetanud, kui niit-1 soovib nüüd käivituda, peab see uuesti ootama, kuni lõim-2 vabastab GIL-i.
Seetõttu saab tõlkile igal ajal juurde pääseda ainult üks lõim, mis tähendab, et antud ajahetkel on ainult üks niit, mis täidab pythoni koodi.
Ühetuumalises protsessoris on see korras, kuna see kasutaks lõimede käsitlemiseks aja viilutamist (vt selle õpetuse esimest jaotist). Mitmetuumaliste protsessorite korral mõjutab mitmel niidil töötav protsessoriga seotud funktsioon aga märkimisväärselt programmi tõhusust, kuna tegelikult ei kasutata kõiki saadaolevaid südamikke korraga.
Miks oli vaja GIL-i?
CPythoni prügikoguja kasutab tõhusat mäluhaldustehnikat, mida nimetatakse viitearvestuseks. See toimib järgmiselt: Igal pythoni objektil on viitenumber, mida suurendatakse, kui see määratakse uuele muutuja nimele või lisatakse konteinerisse (näiteks rühmad, loendid jne). Samamoodi väheneb viidete arv, kui viide väljub reguleerimisalast või kui kutsutakse del-lauset. Kui objekti võrdlusarv jõuab 0-ni, kogutakse see prügi ja eraldatud mälu vabaneb.
Kuid probleem on selles, et võrdlusarvu muutuja on altid võistlustingimustele nagu iga teine globaalne muutuja. Selle probleemi lahendamiseks otsustasid pythoni arendajad kasutada globaalset tõlgi lukku. Teine võimalus oli lisada igale objektile lukk, mis oleks kaasa toonud ummikud ja suurenenud üldkulud hangimis- () ja vabastamiskõnede () korral.
Seetõttu on GIL märkimisväärne piirang mitmekeermeliste pythoni programmide jaoks, mis käitavad raskeid protsessoriga seotud toiminguid (muutes need tegelikult üheahelalisteks). Kui soovite oma rakenduses kasutada mitut protsessori südamikku, kasutage selle asemel mitme protsessori moodulit.
Kokkuvõte
- Python toetab mitmikeermelist 2 moodulit:
- __thread moodul: see pakub keermestamiseks madalat rakendust ja on vananenud.
- keermestusmoodul : See pakub kõrgetasemelist juurutamist mitmikeermeliseks kasutamiseks ja on praegune standard.
- Lõnga loomiseks keermestusmooduli abil peate tegema järgmist.
- Looge klass, mis laiendab lõime klassi.
- Alista selle konstruktor (__init__).
- Alista selle run () meetod.
- Looge selle klassi objekt.
- Lõnga saab käivitada, kutsudes algusmeetodi () .
- Liituda () meetodit saab kasutada blokeerida teiste teemasid kuni selle katkemiseni (sama, kus liituda kutsuti) lõpetab täitmise.
- Võistlusolukord ilmneb siis, kui jagatud ressurssidele pääseb korraga juurde või muudetakse mitut lõime.
- Seda saab vältida niitide sünkroonimisega.
- Python toetab lõimede sünkroonimiseks kuut viisi:
- Lukud
- R-lukud
- Semafoorid
- Tingimused
- Sündmused ja
- Tõkked
- Lukud võimaldavad kriitilisse sektsiooni siseneda ainult kindla niidi, mis on luku omandanud.
- Lukul on kaks peamist meetodit:
- omandama () : see määrab luku oleku lukustatuks. Kui see kutsutakse lukustatud objektile, blokeerib see, kuni ressurss on vaba.
- release () : see määrab luku oleku avatuks ja naaseb. Kui helistatakse lukustamata objektile, tagastab see vale.
- Üldine tõlgi lukustus on mehhanism, mille kaudu saab korraga käivitada ainult 1 CPythoni tõlgi protsessi.
- Seda kasutati CPythonsi prügikoguja võrdlusloendamise funktsionaalsuse hõlbustamiseks.
- Pythoni rakenduste valmistamiseks raskete protsessoriga seotud toimingutega peaksite kasutama mitmeprotsessilist moodulit.