C Programmering

Ditt første C-program ved bruk av gaffelsystemanrop

Ditt første C-program ved bruk av gaffelsystemanrop
Som standard har C-programmer ingen samtidighet eller parallellitet, bare en oppgave skjer om gangen, hver kodelinje blir lest sekvensielt. Men noen ganger må du lese en fil eller - til og med verst - en stikkontakt koblet til en ekstern datamaskin, og dette tar virkelig lang tid for en datamaskin. Det tar vanligvis mindre enn et sekund, men husk at en enkelt CPU-kjerne kan utføre 1 eller 2 milliarder instruksjoner i løpet av den tiden.

Så, som en god utvikler, du vil bli fristet til å instruere C-programmet ditt om å gjøre noe mer nyttig mens du venter. Det er her samtidig programmering er her for din redning - og gjør datamaskinen ulykkelig fordi den må fungere mer.

Her vil jeg vise deg Linux-gaffelsystemanropet, en av de sikreste måtene å gjøre samtidig programmering.

Samtidig programmering kan være utrygt?

Ja det kan det. For eksempel er det også en annen måte å ringe på multithreading. Det har fordelen å være lettere, men det kan egentlig gå galt hvis du bruker det feil. Hvis programmet ditt ved en feiltakelse leser en variabel og skriver til samme variabel samtidig blir programmet usammenhengende, og det kan nesten ikke oppdages - et av de verste marerittene.

Som du vil se nedenfor, kopierer gaffelen minnet, så det er ikke mulig å ha slike problemer med variabler. Gaffel lager også en uavhengig prosess for hver samtidig oppgave. På grunn av disse sikkerhetstiltakene er det omtrent fem ganger tregere å starte en ny samtidig oppgave med gaffel enn med multitråding. Som du kan se, er det ikke mye for fordelene det gir.

Nå, nok med forklaringer, er det på tide å teste ditt første C-program ved hjelp av fork call.

Linux-gaffeleksemplet

Her er koden:

#inkludere
#inkludere
#inkludere
#inkludere
#inkludere
int main ()
pid_t forkStatus;
forkStatus = gaffel ();
/ * Barn ... * /
hvis (forkStatus == 0)
printf ("Barnet kjører, behandler.\ n ");
søvn (5);
printf ("Barnet er ferdig, avsluttende.\ n ");
/ * Forelder ... * /
annet hvis (forkStatus != -1)
printf ("Forelder venter ... \ n");
vent (NULL);
printf ("Forelder går ut ... \ n");
annet
perror ("Feil under tilkalling av gaffelfunksjonen");

retur 0;

Jeg inviterer deg til å teste, kompilere og utføre koden ovenfor, men hvis du vil se hvordan utdataene vil se ut, og du er for "lat" til å kompilere den - når alt kommer til alt er du kanskje en sliten utvikler som samlet C-programmer hele dagen - du finner utdataene fra C-programmet nedenfor sammen med kommandoen jeg brukte til å kompilere det:

$ gcc -std = c89 -Wpedantic -Wall forkSleep.c -o gaffelSove -O2
$ ./ gaffel Sove
Foreldre venter ..
Barnet løper, behandler.
Barnet er ferdig, avslutter.
Forelder går ut ..

Vær ikke redd hvis utdataene ikke er 100% identiske med utdataene mine ovenfor. Husk at å kjøre ting samtidig betyr at oppgaver går ut av ordre, det er ingen forhåndsdefinert bestilling. I dette eksemplet ser du kanskje at barnet løper før foreldre venter, og det er ingenting galt med det. Generelt avhenger bestillingen av kjerneversjonen, antall CPU-kjerner, programmene som for øyeblikket kjører på datamaskinen din, osv.

OK, kom tilbake til koden. Før linjen med gaffel () er dette C-programmet helt normalt: 1 linje kjøres om gangen, det er bare en prosess for dette programmet (hvis det var en liten forsinkelse før gaffelen, kan du bekrefte det i oppgavelederen din).

Etter gaffelen () er det nå to prosesser som kan kjøre parallelt. For det første er det en barneprosess. Denne prosessen er den som er opprettet ved gaffel (). Denne underordnede prosessen er spesiell: den har ikke utført noen av kodelinjene over linjen med gaffel (). I stedet for å lete etter hovedfunksjonen, vil den heller kjøre gaffel () -linjen.

Hva med variablene deklarert før gaffel?

Vel, Linux fork () er interessant fordi den smart svarer på dette spørsmålet. Variabler og faktisk alt minnet i C-programmer blir kopiert til barneprosessen.

La meg definere hva som gjør gaffel med noen få ord: det skaper en klone av prosessen som kaller det. De to prosessene er nesten identiske: alle variablene vil inneholde de samme verdiene, og begge prosessene vil utføre linjen like etter gaffelen (). Imidlertid etter kloningsprosessen, de er atskilt. Hvis du oppdaterer en variabel i den ene prosessen, den andre prosessen vil ikke ha sin variabel oppdatert. Det er virkelig en klon, en kopi, prosessene deler nesten ingenting. Det er veldig nyttig: du kan forberede mye data og deretter fork () og bruke disse dataene i alle kloner.

Separasjonen starter når gaffel () returnerer en verdi. Den opprinnelige prosessen (den heter foreldreprosessen) får prosess-ID for den klonede prosessen. På den andre siden er den klonede prosessen (denne kalles barneprosessen) får 0-tallet. Nå bør du begynne å forstå hvorfor jeg har satt if / else if uttalelser etter fork () linjen. Ved å bruke returverdien kan du instruere barnet om å gjøre noe annet enn det foreldrene gjør - og tro meg, det er nyttig.

På den ene siden, i eksempelkoden ovenfor, gjør barnet en oppgave som tar 5 sekunder og skriver ut en melding. For å imitere en prosess som tar lang tid, bruker jeg søvnfunksjonen. Så går barnet vellykket ut.

På den andre siden skriver foreldrene ut en melding, vent til barnet går ut og til slutt skriver ut en ny melding. Det faktum at foreldrene venter på barnet er viktig. Som et eksempel venter foreldrene det meste av denne tiden på å vente på barnet sitt. Men jeg kunne ha bedt foreldrene om å gjøre noen form for langvarige oppgaver før jeg ba den vente. På denne måten ville det ha gjort nyttige oppgaver i stedet for å vente - tross alt, dette er grunnen til at vi bruker gaffel (), nei?

Imidlertid, som jeg sa ovenfor, er det veldig viktig at foreldre venter på sine barn. Og det er viktig pga zombie prosesser.

Hvordan venting er viktig

Foreldre vil generelt vite om barn er ferdig med behandlingen. For eksempel vil du kjøre oppgaver parallelt, men du absolutt ikke vil foreldrene skal avslutte før childs er ferdig, for hvis det skjedde, ville shell gi en melding mens childs ikke er ferdig ennå - som er rart.

Ventefunksjonen gjør det mulig å vente til en av barneprosessene er avsluttet. Hvis en forelder ringer 10 ganger fork (), må den også ringe 10 ganger vent (), en gang for hvert barn opprettet.

Men hva skjer hvis foreldrene kaller ventefunksjon mens alle barn har det allerede avsluttet? Det er her zombieprosesser er nødvendig.

Når et barn slutter før foreldrene ringer vent (), vil Linux-kjernen la barnet avslutte men det vil beholde en billett å fortelle barnet har gått ut. Når foreldrene ringer vent (), vil den finne billetten, slette den billetten og vente () -funksjonen kommer tilbake umiddelbart fordi det vet foreldrene trenger å vite når barnet er ferdig. Denne billetten kalles a zombie prosess.

Derfor er det viktig at foreldresamtaler vent (): hvis det ikke gjør det, forblir zombieprosesser i minnet og Linux-kjernen kan ikke ha mange zombieprosesser i minnet. Når grensen er nådd, vil datamaskinen din ikan ikke lage noen ny prosess og så vil du være i en veldig dårlig form: til og med for å drepe en prosess, må du kanskje lage en ny prosess for det. Hvis du for eksempel vil åpne oppgavebehandling for å drepe en prosess, kan du ikke, fordi oppgavebehandling vil trenge en ny prosess. Selv verste, du kan ikke drep en zombieprosess.

Derfor er det viktig å ringe vent: det tillater kjernen rydde opp barneprosessen i stedet for å fortsette å hoper seg opp med en liste over avsluttede prosesser. Og hva om foreldrene slutter uten å ringe vente()?

Heldigvis, når foreldrene blir sagt opp, kan ingen andre ringe vent () på disse barna, så det er det ingen grunn for å beholde disse zombieprosessene. Derfor når en forelder slutter, alle gjenværende zombie prosesser knyttet til denne forelderen blir fjernet. Zombie prosesser er egentlig bare nyttig for å la foreldreprosesser finne ut at et barn ble avsluttet før foreldre ringte vent ().

Nå foretrekker du kanskje å vite noen sikkerhetstiltak for å gi deg den beste bruken av gaffel uten problemer.

Enkle regler for å ha gaffelen som ønsket

For det første, hvis du kjenner til multitråding, må du ikke forkaste et program ved hjelp av tråder. Unngå faktisk å blande flere samtidige teknologier. gaffel antar å jobbe i normale C-programmer, den har kun til hensikt å klone en parallell oppgave, ikke mer.

For det andre, unngå å åpne eller åpne filer før gaffel (). Filer er noe av det eneste delt og ikke klonet mellom foreldre og barn. Hvis du leser 16 byte i foreldre, vil den flytte lesepekeren fremover på 16 byte både hos foreldrene og hos barnet. Verst, hvis barn og foreldre skriver byte til samme fil samtidig kan bytes til foreldrene være blandet med byte av barnet!

For å være tydelig, utenfor STDIN, STDOUT og STDERR, vil du virkelig ikke dele noen åpne filer med kloner.

For det tredje, vær forsiktig med stikkontakter. Stikkontakter er delte også mellom foreldre og barn. Det er nyttig for å lytte til en port og deretter ha flere barnearbeidere klare til å håndtere en ny klientforbindelse. derimot, hvis du bruker det feil, vil du få problemer.

For det fjerde, hvis du vil ringe fork () i en løkke, gjør du dette med ekstrem forsiktighet. La oss ta denne koden:

/ * IKKE KOMPILER DETTE * /
const int targetFork = 4;
pid_t forkResult
 
for (int i = 0; i < targetFork; i++)
forkResult = gaffel ();
/ *… * /
 

Hvis du leser koden, kan du forvente at den lager 4 barn. Men det vil heller skape 16 barn. Det er fordi childs vil også utføre sløyfen, så barn vil i sin tur ringe gaffel (). Når løkken er uendelig, kalles den a gaffelbombe og er en av måtene å bremse et Linux-system så mye at det ikke lenger fungerer og trenger omstart. I et nøtteskall, husk at Clone Wars ikke bare er farlig i Star Wars!

Nå har du sett hvordan en enkel sløyfe kan gå galt, hvordan du bruker sløyfer med gaffel ()? Hvis du trenger en løkke, må du alltid sjekke gaffelens returverdi:

const int targetFork = 4;
pid_t forkResult;
int i = 0;
gjør
forkResult = gaffel ();
/ *… * /
i ++;
mens ((forkResult != 0 && forkResult != -1) && (i < targetFork));

Konklusjon

Nå er det på tide for deg å gjøre dine egne eksperimenter med gaffel ()! Prøv nye måter å optimalisere tid ved å utføre oppgaver på tvers av flere CPU-kjerner eller gjøre bakgrunnsbehandling mens du venter på å lese en fil!

Ikke nøl med å lese manuelle sider via man-kommandoen. Du vil lære om hvordan fork () fungerer nøyaktig, hvilke feil du kan få osv. Og nyt samtidighet!

Beste kommandolinjespill for Linux
Kommandolinjen er ikke bare din største allierte når du bruker Linux, den kan også være kilden til underholdning fordi du kan bruke den til å spille m...
Beste Gamepad Mapping Apps for Linux
Hvis du liker å spille spill på Linux med en gamepad i stedet for et vanlig tastatur- og musinngangssystem, er det noen nyttige apper for deg. Mange P...
Nyttige verktøy for Linux-spillere
Hvis du liker å spille spill på Linux, er sjansen stor for at du har brukt apper og verktøy som Wine, Lutris og OBS Studio for å forbedre spilloppleve...