C Academy · Oppitunti

Estävä ja ei-estävä I/O

Miksi tapahtumasilmukoilla on merkitystä.

Oppitunti 1/413 vaihetta

Estävä ja ei-estävä I/O on ilmainen C Academy-oppitunti CoddyKitissä. Tämä on oppitunti 1/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu C Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. C Academy-kurssilla on yhteensä 4 oppituntia.

Mitä estävä tarkoittaa

Estävä järjestelmäkutsu keskeyttää kutsuvan säikeen, kunnes toiminto voi jatkua. Kun kutsutte recv()-funktiota socketilla, jolla ei ole dataa, ydin pysäyttää säikeen, kunnes tavuja saapuu.

Tätä on helppo ajatella: yksi yhteys, yksi säie ja suoraviivainen koodi. Kustannus näkyy, kun tuhansia asiakkaita on palveltava samanaikaisesti.

ssize_t n = recv(fd, buf, sizeof buf, 0);
/* thread sleeps here until data or error */
if (n > 0) handle(buf, n);

Skaalautuvuusongelma

Estävässä I/O:ssa yksi jumittunut asiakas estää koko säikeen toiminnan. Klassinen ratkaisu on yksi säie tai prosessi yhteyttä kohti.

Se toimii tiettyyn rajaan asti, mutta 10 000 säiettä tarkoittaa 10 000 pinoa, raskasta kontekstinvaihtoa ja ajastimen aiheuttamaa kuormaa. Tämä on kuuluisa C10k-ongelma, joka ohjasi palvelimia tapahtumavetoisiin rakenteisiin.

Estämätön tila

Estämätön socket ei koskaan jää odottamaan. Jos kutsua ei voida suorittaa heti, se palauttaa välittömästi arvon -1 ja asettaa errno-muuttujan arvoksi EAGAIN tai EWOULDBLOCK.

Koodinne vastuulla on yrittää myöhemmin uudelleen. Näin yksi säie voi käsitellä useita socketteja juuttumatta koskaan yhteen niistä.

ssize_t n = recv(fd, buf, sizeof buf, 0);
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
    /* no data right now, try again later */
}

O_NONBLOCK-asetuksen määrittäminen fcntl-funktiolla

Descriptor vaihdetaan estämättömäksi lisäämällä siihen O_NONBLOCK-lippu fcntl()-funktion avulla. Lukekaa aina ensin nykyiset liput ja tehkää niihin sitten bittitasoinen OR-operaatio, jotta muut asetukset eivät ylikirjoitu.

Samaa apufunktiota käytetään kuunteleviin socketteihin, hyväksyttyihin asiakassocketteihin ja putkiin.

int set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) return -1;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

Osittaisten lukujen käsittely

Estämätön I/O tekee osittaisista operaatioista tavallisia. recv() voi palauttaa pyydettyä vähemmän tavuja, ja send() voi hyväksyä puskurista vain osan.

Teidän on seurattava, kuinka paljon on lähetetty tai vastaanotettu, ja jatkettava siitä kohdasta. Älkää koskaan olettako, että yksi kutsu siirtää kaikki tavut.

size_t sent = 0;
while (sent < len) {
    ssize_t w = send(fd, buf + sent, len - sent, 0);
    if (w < 0) { if (errno == EAGAIN) break; else return -1; }
    sent += w;
}

Aktiivinen odotus on väärin

Naivi tapa käyttää estämättömiä socketteja on käydä ne kaikki silmukassa läpi ja yrittää jatkuvasti uudelleen. Tämä aktiivinen odotus kuluttaa 100 % suorittimesta, vaikka mitään ei tapahtuisi.

Haluamme oikeastaan kysyä ytimeltä: "ilmoita, mitkä descriptorit ovat valmiita, ja anna minun nukkua siihen asti". Juuri tämän valmiusilmoitus tarjoaa.

Valmiusilmoitus

I/O:n multipleksoinnin ansiosta yksi säie voi odottaa useita descriptoreita samanaikaisesti ja herätä vasta, kun vähintään yksi niistä on valmis. Ydin huolehtii seurannasta puolestanne.

Klassiset rajapinnat ovat select() ja poll(). Ne toimivat, mutta käyvät jokaisella kutsulla kaikki descriptorit uudelleen läpi, mikä käy kalliiksi suuressa mittakaavassa.

fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
select(fd + 1, &rfds, NULL, NULL, NULL);

Miksi select ja poll eivät skaalaudu

Sekä select() että poll() ovat O(n): jokainen kutsu välittää koko descriptorijoukon ytimelle, joka käy ne kaikki läpi, minkä jälkeen käytte ne kaikki uudelleen läpi löytääksenne valmiit descriptorit.

select() saavuttaa lisäksi rajan noin arvossa FD_SETSIZE (usein 1024). Tuhansilla yhteyksillä tämä kuormitus hallitsee suorituskykyä.

epoll käyttöön

epoll on Linuxin skaalautuva ratkaisu. Rekisteröitte kiinnostuksen descriptorista kerran, minkä jälkeen ydin ylläpitää sisäistä valmiutta seuraavaa tietorakennetta.

Jokainen odotus palauttaa vain todella valmiit descriptorit, joten kustannus skaalautuu aktiivisten eikä kaikkien yhteyksien mukaan. Näin kustannus on suunnilleen O(1) kutakin valmista tapahtumaa kohti.

int epfd = epoll_create1(0);
/* register fds once, then wait for ready events */

Estämättömät socketit ja epoll

epoll ja estämättömät socketit toimivat yhdessä. epoll ilmoittaa, että descriptor on valmis; estämättömät kutsut mahdollistavat sen tyhjentämisen ilman odottamista.

Asettakaa aina O_NONBLOCK epollille annettaviin socketteihin. Muuten virheellinen herätys tai osittainen luku voisi estää yksisäikeisen tapahtumasilmukan.

set_nonblocking(conn_fd);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = conn_fd };
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);

Ajattelumalli

Ajatelkaa palvelinta silmukkana: odotetaan epoll_wait()-funktiossa, saadaan pieni luettelo valmiista descriptoreista, tehdään kullekin estämätöntä työtä ja toistetaan.

Säie nukkuu toimettomana ja herää vain todellista työtä varten. Yksi säie voi nyt palvella tehokkaasti kymmeniätuhansia yhteyksiä.

Pikatarkistus

Testaa ymmärryksesi estämättömistä socketeista.

Kertaus

Estävä I/O on yksinkertaista, mutta sitoo yhden säikeen jokaiseen yhteyteen, eikä tämä skaalaudu. Estämätön I/O palauttaa välittömästi arvon EAGAIN sen sijaan, että jäisi odottamaan.

Socketien pollaaminen tiiviissä silmukassa tuhlaa suorittimen aikaa, joten käytämme valmiusilmoitusta. select/poll ovat O(n); epoll skaalautuu moniin tuhansiin yhteyksiin. Seuraavaksi määritämme epollin käyttöön.

Aloita maksutta

Opi C tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
39
Oppitunnit
144

Usein kysytyt kysymykset

Onko oppitunti ”Estävä ja ei-estävä I/O” ilmainen?

Kyllä – oppitunnin ”Estävä ja ei-estävä I/O” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko C Academy-kurssin, päivitä CoddyKit PROhon. C Academy-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Estävä ja ei-estävä I/O”?

Miksi tapahtumasilmukoilla on merkitystä. Harjoittelet C Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni C Academy-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin C Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 1/4.

Kuinka kauan ”Estävä ja ei-estävä I/O”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä C Academy-oppitunnilla?

Kyllä. Jokainen C Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. Estävä ja ei-estävä I/O
  2. epollin käyttöönotto
  3. Tapahtumasilmukka
  4. Yksinkertainen kaikupalvelin
← Takaisin: C Academy