A számítástechnikában az aszinkron I/O a nem blokkoló I/O feldolgozás egyik formája, amely lehetővé teszi a folyamatok folytatását anélkül, hogy megvárná az adatátvitel befejezését .
A számítógépen a bemeneti és kimeneti (I/O) műveletek meglehetősen lassúak lehetnek az adatfeldolgozáshoz képest. Az I/O eszköz több nagyságrenddel lassabb lehet, mint a RAM. Például egy lemezművelet során, amely tíz milliszekundumot vesz igénybe, az egy gigahertzen futó processzor tízmillió feldolgozási utasítás ciklust tud végrehajtani.
I/O típusok és példák Unix I/O funkciókra :
Blokkolás | nem blokkoló | |
---|---|---|
Szinkron | írni olvasni | írás, olvasás + lekérdezés / választás |
Aszinkron | - | aio_write, aio_read |
A nem blokkoló I/O előnye a CPU erőforrások hatékony felhasználása. Például a GUI-alkalmazásokban a klasszikus blokkoló I/O blokkolhatja az eseményhurkot egy hosszú műveletnél, és az alkalmazást nem reagálja a felhasználói interakcióra, mivel blokkolja az eseményhurkot futtató teljes végrehajtási szálat. Ezenkívül a nem blokkoló I/O-t olyan hálózati alkalmazásokban használják, ahol egy végrehajtási szálban (folyamatban) több klienst kell egyszerre kiszolgálni. A blokkoló megközelítéssel egyetlen "lassú" kliens lelassítja a teljes szálat.
Tehát mi a különbség a nem blokkoló I/O aszinkron és szinkron megközelítése között? A második esetben a blokkolást elkerüljük a bejövő adatok meglétének vagy a kimenő adatok írásának lehetőségének ellenőrzésével. Az aszinkron megközelítésben nincs szükség érvényesítésre. Az aszinkron név azt jelenti, hogy "elveszítjük" az irányítást az I/O műveletek sorrendje felett. A sorrendet az operációs rendszer határozza meg, amely az I / O eszközök elérhetősége alapján építi fel a műveleteket. [egy]
A programírás aszinkron megközelítése nehezebb, de nagyobb hatékonyságot tesz lehetővé. Példa erre egy Linux rendszerhívás epollés Microsoft Windows átfedett I /O összehasonlítása . egy példa a nem blokkoló szinkron I/O-ra, és lekérdezi a fájlleírók listáját, hogy készen álljon a műveletek végrehajtására. Hatékony a hálózati I/O vagy különféle típusú folyamatok közötti kommunikációhoz, mivel ezek a műveletek magukban foglalják az adatok másolását a kernel puffereiből és a kernel pufferekbe, és nem fogyasztanak jelentős CPU-időt. Ez a rendszerhívás azonban nem hatékony lassabb fájl I/O-val. Például: ha van néhány adat a fájlban, akkor az olvasás blokkolja a folyamatot, amíg ki nem olvassa a lemezről és átmásolja a megadott pufferbe. A Windows megközelítése más: meghívja a függvényt , átadva neki egy puffert, amelybe írhat, és egy fájlleírót. Ez a funkció csak olvasási műveletet kezdeményez, és azonnal visszaadja a folyamat vezérlését. Mire a háttérben lévő operációs rendszer beolvassa az adatokat a fájlból a pufferbe, jelezni fogja a folyamatnak, hogy a művelet befejeződött, akár a függvénynek átadott visszahíváson , akár az I/O Completion Porton (IOCP) keresztül. A visszahívási funkció csak a művelet(ek) befejezésére várva hívódik meg. [2]epollReadFileReadFile
Az alkalmazáshoz biztosított API-k fajtái nem feltétlenül felelnek meg az operációs rendszer által ténylegesen biztosított mechanizmusoknak, emuláció lehetséges.
Elérhető FreeBSD , OS X , VMS és Windows rendszereken .
A lehetséges probléma az, hogy a veremmélység ellenőrizhetetlenül növekedhet, ezért a rendkívül kulcsfontosságú dolog az, hogy csak akkor ütemezzen be egy újabb I/O-t, amikor az előző befejeződött. Ha azonnal teljesíteni kell, akkor a kezdeti visszahívás nem „tekeri fel” a veremet a következő hívása előtt. Az ezt megakadályozó rendszerek (például a következő munka „középponti” ütemezése) növelik a bonyolultságot és csökkentik a termelékenységet. A gyakorlatban azonban ez általában nem jelent problémát, mivel a következő I/O maga általában azonnal visszatér, amint a következő I/O elindult, lehetővé téve a verem „letekerését”. A probléma az sem előzhető meg, ha elkerüli a további visszahívásokat egy sor használatával, amíg az első visszahívás vissza nem tér.
A korutinok (korutinok) lehetővé teszik, hogy aszinkron programokat szinkron stílusban írjunk. Példák:
Számos könyvtár is létezik a korutinok létrehozására (libcoro [3] , Boost Coroutine)
Elérhető Microsoft Windows , Solaris és DNIX rendszereken . Az I/O kérések aszinkron módon kerülnek kiadásra, de a végrehajtási értesítések a szinkronizálási sor mechanizmuson keresztül a végrehajtásuk sorrendjében jelennek meg. Általában a fő folyamatot strukturáló állapotgéphez ( eseményvezérelt programozás ) társul , amely alig hasonlít egy olyan folyamathoz, amely nem használ aszinkron I/O-t, vagy amely más formákat használ, megnehezítve a kód újrafelhasználását. Nem igényel további speciális szinkronizálási mechanizmusokat vagy szálbiztos könyvtárakat, valamint a szöveg (kód) és az időbeli (esemény) folyamok elkülönülnek.
Az IBM , Groupe Bull és Unisys nagyszámítógépeken elérhető I/O csatornákat úgy tervezték, hogy maximalizálják a CPU- és a sávszélesség-használatot azáltal, hogy I/O-t hajtanak végre a társprocesszoron. A társprocesszor rendelkezik DMA -val , kezeli az eszközmegszakításokat, a CPU vezérli, és csak akkor szakítja meg a főprocesszort, ha valóban szükség van rá. Ez az architektúra támogatja az úgynevezett csatornaprogramokat is, amelyek a csatornaprocesszoron futnak, hogy elvégezzék az I/O tevékenységek és protokollok nehézkes emelését.
Az általános célú számítástechnikai berendezések túlnyomó többsége teljes mértékben két módszerre támaszkodik az aszinkron I/O megvalósítására: lekérdezésre és megszakításra. Általában mindkét módszert együtt alkalmazzák, az egyensúly nagymértékben függ a hardver kialakításától és a szükséges jellemzőitől. ( A DMA önmagában nem egy másik független módszer, csak egy eszköz, amellyel minden lekérdezéssel vagy megszakítással több munka végezhető el.)
A csak lekérdezéses rendszerek általában lehetségesek, kis mikrokontrollerek (például PIC -ket használó rendszerek ) gyakran így épülnek fel. A CP/M rendszereket így is meg lehet építeni (bár ritkán volt ilyen), DMA-val vagy anélkül. Továbbá, ha a lehető legjobb teljesítményre csak néhány feladathoz van szükség, bármely más lehetséges feladat rovására, a lekérdezés még megfelelőbb lehet, mivel a megszakításokkal járó többlet nem kívánatos. (A megszakítások kiszolgálása időt és helyet igényel, hogy a processzor állapotának legalább egy részét eltárolják, mielőtt a megszakított feladatot folytatni kellene.)
A legtöbb általános célú számítástechnikai rendszer nagymértékben támaszkodik a megszakításokra. Létezhet csak megszakításos rendszer, bár általában szükség van némi lekérdezésre. Gyakran több potenciális megszakítási forrás osztozik egy közös megszakítási jelvonalon, ebben az esetben az eszközillesztő lekérdezést használ a tényleges forrás kiderítésére. (Ezúttal ennek kiderítése hozzájárul a rendszermegszakítások teljesítményének romlásához. Az évek során rengeteg munkát végeztek a kiszolgálási megszakításokkal járó többletterhelés minimalizálása érdekében. A modern megszakítási rendszerek lassúnak mondhatók néhány jól optimalizált rendszerhez képest. , a korábbi verziók megvalósításai, de a hardver teljesítményének általános növekedése ezt jelentősen csökkentette.)
Hibrid megközelítések is lehetségesek, ahol egy megszakítás egy kis aszinkron I/O sorozatot indíthat el, és a lekérdezés magában ebben a burstban történik. Ez a technika gyakori a nagysebességű eszköz-illesztőprogramokban, például a hálózatban vagy a lemezen, ahol a megszakítás előtt futó feladathoz való visszatérés ideje hosszabb, mint a következő szükséges karbantartásig. (A manapság használatos általános célú I/O hardver nagymértékben támaszkodik a DMA-ra és a nagy adatpufferekre, hogy pótolja a viszonylag lassú megszakítási rendszer hátrányait. Általánosan elterjedt a meghajtó főhurkán belüli lekérdezés használata , ami hatalmas átviteli sebességet biztosít ( ideális esetben a szavazások mindig sikeresek, amikor megjelennek az adatok, vagy legfeljebb az ismétlések száma kicsi).
Egy időben ez a fajta hibrid megközelítés általános volt a lemez- és hálózati illesztőprogramokban, ahol nem volt DMA vagy jelentős pufferelési képesség. Mivel a várt átviteli sebességek magasabbak voltak, mint négy művelet egy minimális feldolgozási ciklusban (bitteszt, feltételes elágazás, lekérés és tárolás), a hardver gyakran úgy épül fel, hogy automatikusan várakozási állapotot generáljon az I/ O eszközön a lekérdezési adatok készenléte a szoftverről a processzorban lévő lekérési-tárolási hardverre kerül, és ezáltal a programciklus-műveletek száma kettőre csökken. (Valóban, magát a processzort használva DMA végrehajtóként). A 6502-es processzor szokatlan lehetőséget kínált az adatok megjelenését kezelő hurok három elemének biztosítására, mivel van egy hardveres tű, amely aktiválásakor közvetlenül beállítja a processzor túlcsordulási bitjét. (Nyilvánvalóan nagy körültekintéssel kell eljárni a hardver tervezésénél, hogy elkerüljük a túlcsordulási bit újradefiniálását a meghajtón kívül!)
Ezekben a példákban a Pythonban mindhárom I/O-típust az olvasás példájával tekintjük. Az I/O objektumok és funkciók absztraktak, és csak példaként szolgálnak.
1. Blokkolás, szinkron:
eszköz = IO . nyitott () adat = eszköz . olvassa () # a folyamat blokkolva lesz, amíg nincs adat az eszköznyomtatásban ( adat )2. Nem blokkoló, szinkron:
eszköz = IO . open () while True : is_ready = IO . poll ( eszköz , IO . INPUT , 5 ) # ne várjon 5 másodpercnél tovább, amíg lehetőség nyílik az eszközről történő olvasásra (INPUT), ha is_ready : data = eszköz . read () # a folyamat nem blokkol, mert megbizonyosodtunk arról, hogy olvasható break # kitörés a hurokból else : print ( "nincs adat az eszközben!" )3. Nem blokkoló, aszinkron:
ios = IO . IOService () eszköz = IO . nyitott ( ios ) def inputHandler ( adatok , err ): "Adatjelenléti eseménykezelő" ha nem err : print ( adat ) készülék . readSome ( inputHandler ) ios . loop () # várja meg a művelet végét a szükséges kezelők hívásához. Ha nincs több művelet, akkor a ciklus visszaadja a vezérlést.A reaktor mintázata aszinkronnak is tulajdonítható :
eszköz = IO . nyitott () reaktor = IO . Reaktor () def inputHandler ( data ): " Adatjelenléti eseménykezelő " print ( adat ) reaktor . megáll () reaktor . addHandler ( inputHandler , eszköz , IO . INPUT ) reaktor . run () # elindítja a reaktort, amely válaszol az I/O eseményekre és hívja a szükséges kezelőket