În informatică , I/O asincron este o formă de procesare I/O non-blocante care permite unui proces să continue executarea fără a aștepta finalizarea transferului de date .
Operațiunile de intrare și ieșire (I/O) pe un computer pot fi destul de lente în comparație cu procesarea datelor. Dispozitivul I/O poate fi cu câteva ordine de mărime mai lent decât RAM. De exemplu, în timpul unei operațiuni pe disc care durează zece milisecunde pentru a se finaliza, un procesor care rulează la un gigahertz poate executa zece milioane de cicluri de instrucțiuni de procesare.
Tipuri de I/O și exemple de funcții Unix I/O :
Blocare | neblocante | |
---|---|---|
Sincron | scrie citeste | scrie, citește + sondaj / selectează |
Asincron | - | aio_write, aio_read |
Avantajul I/O neblocante este utilizarea eficientă a resurselor CPU. De exemplu, în aplicațiile GUI, blocarea I/O clasică poate bloca bucla de evenimente la o operațiune lungă și poate face ca aplicația să nu răspundă la interacțiunea utilizatorului prin blocarea întregului fir de execuție care rulează bucla de evenimente. De asemenea, I/O neblocante este folosit în aplicațiile de rețea în care este necesar să deserviți simultan mai mulți clienți într-un fir (proces) de execuție. Cu o abordare de blocare, doar un client „lent” ar încetini întregul fir.
Deci, care este diferența dintre o abordare asincronă și una sincronă a I/O neblocante? În al doilea caz, blocarea este evitată prin verificarea prezenței datelor de intrare sau a posibilității de a scrie datele de ieșire. În abordarea asincronă, nu este necesară nicio validare. Numele asincron înseamnă că „pierdem” controlul asupra ordinii operațiunilor I/O. Ordinea este determinată de sistemul de operare, care construiește operațiuni pe baza disponibilității dispozitivelor I/O. [unu]
Abordarea asincronă a scrierii unui program este mai dificilă, dar permite o mai mare eficiență. Un exemplu ar fi compararea unui apel de sistem epollpe Linux și I/O suprapus pe Microsoft Windows . epolleste un exemplu de I/O sincronă neblocante și interogează o listă de descriptori de fișiere pentru a fi pregătiți pentru a efectua operațiuni. Este eficient pentru I/O în rețea sau pentru diferite tipuri de comunicații între procese, deoarece aceste operațiuni implică copierea datelor din și în buffer-urile kernelului și nu consumă timp CPU semnificativ. Cu toate acestea, acest apel de sistem este ineficient cu I/O de fișiere mai lente. De exemplu: dacă există anumite date în fișier, citirea acestuia va bloca procesul până când este citită de pe disc și copiată în memoria tampon furnizată. Abordarea Windows este diferită: apelați funcția ReadFile, oferindu-i un buffer în care să scrieți și un descriptor de fișier. Această funcție inițiază doar o operație de citire și readuce imediat controlul procesului. În momentul în care sistemul de operare din fundal citește datele din fișier în buffer, acesta va semnala procesului că operațiunea este finalizată, fie prin apelarea ReadFile inversă transmisă funcției , fie prin portul de completare I/O (IOCP). Funcția de apel invers va fi apelată numai în timp ce se așteaptă finalizarea operațiunilor. [2]
Tipurile de API-uri furnizate aplicației nu corespund neapărat mecanismelor furnizate efectiv de sistemul de operare, emularea fiind posibilă.
Disponibil pe FreeBSD , OS X , VMS și Windows .
Problema potențială este că adâncimea stivei poate crește necontrolat, așa că lucrul extrem de important de făcut este să programați un alt I/O numai când s-a finalizat cel anterior. Dacă trebuie să fie satisfăcut imediat, apelul inițial nu „desface” stiva înainte ca următorul să fie apelat. Sistemele pentru a preveni acest lucru (cum ar fi programarea „la mijloc” a următoarei lucrări) măresc complexitatea și reduc productivitatea. În practică, totuși, aceasta nu este de obicei o problemă, deoarece următorul I/O va reveni de obicei imediat ce următorul I/O a început, permițând „dezvoltare” stivei. De asemenea, problema nu poate fi prevenită prin evitarea oricăror apeluri inverse ulterioare folosind o coadă până când se întoarce primul apel invers.
Coroutine (coroutine) vă permit să scrieți programe asincrone într-un stil sincron. Exemple:
Există, de asemenea, multe biblioteci pentru crearea de coroutine (libcoro [3] , Boost Coroutine)
Disponibil pe Microsoft Windows , Solaris și DNIX . Solicitările I/O sunt emise asincron, dar notificările de execuție sunt furnizate prin mecanismul cozii de sincronizare în ordinea în care sunt finalizate. Asociat de obicei cu o mașină de stare care structurează procesul principal ( programare bazată pe evenimente ), care poate să semene puțin cu un proces care nu utilizează I/O asincron sau care utilizează una dintre celelalte forme, ceea ce face dificilă reutilizarea codului. Nu necesită mecanisme speciale de sincronizare suplimentare sau biblioteci sigure pentru fire , precum și fluxurile de text (cod) și temporale (evenimente) sunt separate.
Disponibil pe mainframe IBM , Groupe Bull și Unisys , canalele I/O sunt proiectate pentru a maximiza utilizarea CPU și a lățimii de bandă prin efectuarea I/O pe coprocesor. Coprocesorul are un DMA la bord , se ocupă de întreruperile dispozitivului, este controlat de CPU și întrerupe procesorul principal doar atunci când este cu adevărat necesar. Această arhitectură acceptă, de asemenea, așa-numitele programe de canal care rulează pe procesorul de canal pentru a realiza activitățile și protocoalele I/O.
Marea majoritate a echipamentelor de calcul de uz general se bazează în întregime pe două metode de implementare a I/O asincrone: interogare și întreruperi. De obicei, ambele metode sunt utilizate împreună, echilibrul depinde în mare măsură de designul hardware-ului și de caracteristicile necesare. ( DMA în sine nu este o altă metodă independentă, este doar un mijloc prin care se poate lucra mai mult cu fiecare sondaj sau întrerupere.)
În general, sunt posibile sisteme numai de sondare, microcontrolerele mici (cum ar fi sistemele care utilizează PIC -uri ) sunt adesea construite în acest fel. Sistemele CP/M pot fi, de asemenea, construite în acest fel (deși au fost rareori), cu sau fără DMA. De asemenea, atunci când este nevoie de cea mai bună performanță posibilă doar pentru câteva sarcini, în detrimentul oricăror alte sarcini potențiale, sondarea poate fi chiar mai adecvată, deoarece suprasolicitarea asociată întreruperilor poate fi nedorită. (Întreruperile de întreținere necesită timp și spațiu pentru a stoca cel puțin o parte din starea procesorului înainte de a ajunge timpul pentru a relua sarcina întreruptă.)
Majoritatea sistemelor de calcul de uz general se bazează în mare măsură pe întreruperi. Poate exista un sistem numai de întrerupere, deși de obicei este necesară o anumită interogare. Adesea, mai multe surse potențiale de întrerupere împărtășesc o linie comună de semnal de întrerupere, caz în care un sondaj este folosit de driverul dispozitivului pentru a afla sursa reală. (De data aceasta, descoperirea contribuie la degradarea performanței întreruperii sistemului. De-a lungul anilor, s-a depus multă muncă pentru a încerca să minimizeze supraîncărcarea asociată întreruperilor de service. Se poate spune că sistemele moderne de întrerupere sunt lente în comparație cu unele bine optimizate. , implementări ale versiunilor anterioare, dar creșterea generală a performanței hardware a atenuat acest lucru considerabil.)
Sunt posibile abordări hibride, în care o întrerupere poate provoca pornirea unei mici explozii de I/O asincrone, iar interogarea se face chiar în acea explozie. Această tehnică este obișnuită în driverele de dispozitiv de mare viteză, cum ar fi rețea sau disc, unde timpul pierdut în revenirea la sarcina care rulează înainte de întrerupere este mai mare decât timpul până la următoarea întreținere necesară. (Hardware-ul I/O de uz general utilizat în zilele noastre se bazează în mare măsură pe DMA și pe buffer-uri mari de date pentru a compensa dezavantajul unui sistem de întrerupere relativ lent. Este obișnuit să se folosească sondajul în bucla principală a driverului , care poate debitul uriaș ( în mod ideal, sondajele au întotdeauna succes atunci când apar date sau cel mult numărul de repetări este mic).
La un moment dat, acest tip de abordare hibridă era obișnuită în driverele de disc și de rețea unde nu exista DMA sau capacitate semnificativă de tamponare. Deoarece ratele de transfer așteptate erau mai mari decât puteau fi efectuate chiar și patru operațiuni într-un ciclu minim de procesare (test de biți, retragere condiționată, preluare și stocare), adesea hardware-ul este construit pentru a genera automat o stare de așteptare pe I/ Pe dispozitiv, disponibilitatea datelor de interogare este transferată de la software la hardware-ul de stocare de preluare din procesor și, prin urmare, numărul de operațiuni ciclului programului este redus la două. (Într-adevăr, folosind procesorul în sine ca executor DMA). Procesorul 6502 a oferit un mijloc neobișnuit de a oferi trei elemente ale buclei care se ocupă de aspectul datelor, deoarece există un pin hardware care, atunci când este declanșat, setează direct bitul de overflow al procesorului. (Evident că trebuie avută mare grijă în designul hardware pentru a evita redefinirea bitului de overflow în afara driverului!)
În aceste exemple, toate cele trei tipuri de I/O din Python sunt luate în considerare folosind exemplul de citire. Obiectele și funcțiile I/O sunt abstracte și servesc doar ca exemplu.
1. Blocare, sincron:
dispozitiv = IO . open () data = device . read () # procesul se va bloca până când există date în imprimarea dispozitivului ( date )2. Neblocant, sincron:
dispozitiv = IO . open () în timp ce True : is_ready = IO . sondaj ( dispozitiv , IO . INTRARE , 5 ) # așteptați nu mai mult de 5 secunde pentru o oportunitate de a citi (INPUT) de pe dispozitiv dacă is_ready : data = device . read () # procesul nu se va bloca pentru că ne-am asigurat că este lizibil break # break out of the loop else : print ( „nu există date în dispozitiv!” )3. Neblocant, asincron:
ios = IO . IOService () device = IO . deschis ( ios ) def inputHandler ( data , err ): „Manager de evenimente de prezență a datelor” dacă nu err : print ( data ) dispozitiv . readSome ( inputHandler ) ios . loop () # așteptați sfârșitul operațiunii pentru a apela handlerii necesari. Dacă nu mai există operații, bucla va reveni controlul.Modelul reactorului poate fi, de asemenea, atribuit asincronului :
dispozitiv = IO . reactor deschis () = IO . reactor () def inputHandler ( data ): "Manager de evenimente de prezență a datelor" print ( data ) reactor . opri () reactor . reactor addHandler ( inputHandler , dispozitiv , IO . INPUT ) . run () # pornește reactorul, care va răspunde la evenimentele I/O și va apela handlerii necesari