C Programmering

Dit første C-program ved hjælp af Fork System Call

Dit første C-program ved hjælp af Fork System Call
Som standard har C-programmer ingen samtidighed eller parallelisme, kun en opgave sker ad gangen, hver linje kode læses sekventielt. Men nogle gange skal du læse en fil eller - endda værst - et stik tilsluttet en fjerncomputer, og det tager virkelig lang tid for en computer. Det tager normalt mindre end et sekund, men husk, at en enkelt CPU-kerne kan udføre 1 eller 2 milliarder af instruktioner i løbet af den tid.

Så, som en god udvikler, du vil blive fristet til at instruere dit C-program om at gøre noget mere nyttigt, mens du venter. Det er her, samtidig programmering er her for din redning - og gør din computer utilfreds, fordi den skal arbejde mere.

Her viser jeg dig Linux-gaffelsystemopkaldet, en af ​​de sikreste måder at udføre samtidig programmering på.

Samtidig programmering kan være usikker?

Ja den kan. For eksempel er der også en anden måde at ringe på multithreading. Det har fordelen at være lettere, men det kan det virkelig gå galt, hvis du bruger det forkert. Hvis dit program ved en fejltagelse læser en variabel og skriver til samme variabel på samme tid bliver dit program usammenhængende, og det kan næsten ikke detekteres - et af de værste udvikleres mareridt.

Som du vil se nedenfor, kopierer gaffel hukommelsen, så det er ikke muligt at have sådanne problemer med variabler. Gaffel laver også en uafhængig proces for hver samtidig opgave. På grund af disse sikkerhedsforanstaltninger er det cirka 5 gange langsommere at starte en ny samtidig opgave ved hjælp af gaffel end med multithreading. Som du kan se, er det ikke meget for de fordele, det medfører.

Nu, nok af forklaringer, er det tid til at teste dit første C-program ved hjælp af fork call.

Linux-gaffeleksemplet

Her er koden:

#omfatte
#omfatte
#omfatte
#omfatte
#omfatte
int main ()
pid_t forkStatus;
forkStatus = gaffel ();
/ * Barn ... * /
hvis (forkStatus == 0)
printf ("Barn kører, behandler.\ n ");
søvn (5);
printf ("Child is done, exiting.".\ n ");
/ * Forælder ... * /
ellers hvis (forkStatus != -1)
printf ("Forælder venter ... \ n");
vent (NULL);
printf ("Forælder går ud ... \ n");
andet
perror ("Fejl under opkald til gaffelfunktionen");

returnere 0;

Jeg opfordrer dig til at teste, kompilere og udføre koden ovenfor, men hvis du vil se, hvordan output ser ud, og du er for "doven" til at kompilere den - når alt kommer til alt er du måske en træt udvikler, der kompilerede C-programmer hele dagen - du kan finde output fra C-programmet nedenfor sammen med den kommando, jeg brugte til at kompilere det:

$ gcc -std = c89 -Wpedantic -Væg forkSleep.c-o gaffel Sove-O2
$ ./ forkSleep
Forælder venter ..
Barn kører, behandler.
Barnet er færdigt, spændende.
Forælder går ud ..

Vær ikke bange, hvis output ikke er 100% identisk med min output ovenfor. Husk, at kørsel af ting på samme tid betyder, at opgaver løber uden for ordre, der er ingen foruddefineret ordre. I dette eksempel kan du muligvis se, at barnet kører Før forælder venter, og der er ikke noget galt med det. Generelt afhænger rækkefølgen af ​​kerneversionen, antallet af CPU-kerner, de programmer, der kører på din computer osv.

OK, kom nu tilbage til koden. Før linjen med gaffel () er dette C-program helt normalt: 1 linje udføres ad gangen, der er kun en proces til dette program (hvis der var en lille forsinkelse før gaffel, kunne du bekræfte det i din task manager).

Efter fork () er der nu 2 processer, der kan køre parallelt. For det første er der en barneproces. Denne proces er den, der er oprettet ved fork (). Denne underordnede proces er speciel: den har ikke udført nogen af ​​kodelinjerne over linjen med fork (). I stedet for at lede efter hovedfunktionen, vil den snarere køre gaffel () linjen.

Hvad med de variabler, der er deklareret før gaffel?

Nå, Linux fork () er interessant, fordi det smart svarer på dette spørgsmål. Variabler og faktisk al hukommelse i C-programmer kopieres til barneprocessen.

Lad mig definere, hvad der gør fork i et par ord: det skaber en klon af processen, der kalder det. De 2 processer er næsten identiske: alle variabler indeholder de samme værdier, og begge processer udfører linjen lige efter fork (). Men efter kloningsprocessen, de er adskilt. Hvis du opdaterer en variabel i den ene proces, den anden proces vil ikke få sin variabel opdateret. Det er virkelig en klon, en kopi, processerne deler næsten ingenting. Det er virkelig nyttigt: du kan forberede en masse data og derefter fork () og bruge disse data i alle kloner.

Adskillelsen starter, når fork () returnerer en værdi. Den oprindelige proces (den hedder forældreprocessen) får proces-id for den klonede proces. På den anden side kaldes den klonede proces (denne kaldes barneprocessen) får 0-nummeret. Nu skal du begynde at forstå, hvorfor jeg har sat if / else if udsagn efter fork () linjen. Ved hjælp af returværdi kan du bede barnet om at gøre noget andet end det, forældrene laver - og tro mig, det er nyttigt.

På den ene side, i eksemplet ovenfor, udfører barnet en opgave, der tager 5 sekunder og udskriver en besked. For at efterligne en proces, der tager lang tid, bruger jeg søvnfunktionen. Derefter går barnet med succes.

På den anden side udskriver forælderen en besked, vent indtil barnet går ud og udskriver endelig en anden besked. Det faktum, at forældre venter på sit barn er vigtigt. Som et eksempel venter forældrene det meste af denne tid på at vente på sit barn. Men jeg kunne have instrueret forældren til at udføre enhver form for langvarige opgaver, før han bad den vente. På denne måde ville det have gjort nyttige opgaver i stedet for at vente - når alt kommer til alt er det derfor, vi bruger gaffel (), nej?

Men som jeg sagde ovenfor, er det virkelig vigtigt at forælder venter på sine børn. Og det er vigtigt på grund af zombie processer.

Hvordan det er vigtigt at vente

Forældre vil generelt vide, om børn er færdige med behandlingen. For eksempel vil du køre opgaver parallelt, men du vil bestemt ikke forældrene skal afslutte, før childs er færdige, for hvis det skete, ville shell give en prompt tilbage, mens childs ikke er færdige endnu - hvilket er underligt.

Ventefunktionen gør det muligt at vente, indtil en af ​​barnets processer er afsluttet. Hvis en forælder ringer 10 gange fork (), skal den også ringe 10 gange vent (), en gang for hvert barn oprettet.

Men hvad sker der, hvis forældre ringer til ventefunktion, mens alle børn har det allerede afsluttet? Det er her, zombieprocesser er nødvendige.

Når et barn går ud, før en forælder ringer på vent (), lader Linux-kernen barnet komme ud men det holder en billet fortæller barnet er forladt. Derefter, når forældrene ringer vent (), finder den billetten, sletter den billet, og ventetiden () vender tilbage med det samme fordi det ved, at forældrene skal vide, hvornår barnet er færdigt. Denne billet kaldes a zombie proces.

Derfor er det vigtigt, at forældreopkald venter (): hvis det ikke gør det, forbliver zombieprocesser i hukommelsen og Linux-kernen kan ikke hold mange zombieprocesser i hukommelsen. Når grænsen er nået, skal din computer ikan ikke oprette nogen ny proces og så vil du være i en meget dårlig form: også selvom for at dræbe en proces, skal du muligvis oprette en ny proces til det. For eksempel, hvis du vil åbne din task manager for at dræbe en proces, kan du ikke, fordi din task manager har brug for en ny proces. Selv værst, du kan ikke dræb en zombie proces.

Derfor er det vigtigt at ringe til ventetid: det tillader kernen Ryd op barneprocessen i stedet for at fortsætte med at liste sig med en liste over afsluttede processer. Og hvad nu hvis forældrene går ud uden nogensinde at ringe vente()?

Heldigvis, da forældrene opsiges, kan ingen andre ringe på vent () på disse børn, så der er ingen grund at beholde disse zombieprocesser. Derfor, når en forælder går ud, alle resterende zombie processer knyttet til denne forælder fjernes. Zombie processer er virkelig kun nyttigt for at give forældreprocesser mulighed for at finde ud af, at et barn afsluttes før forælderen kaldte vent ().

Nu foretrækker du måske at kende nogle sikkerhedsforanstaltninger for at give dig den bedste brug af gaffel uden problemer.

Enkle regler at have gaffel til at fungere som beregnet

For det første, hvis du kender multithreading, skal du ikke forkaste et program ved hjælp af tråde. Undgå faktisk at blande flere samtidige teknologier. fork antager at arbejde i normale C-programmer, den har kun til hensigt at klone en parallel opgave, ikke mere.

For det andet skal du undgå at åbne eller åbne filer før fork (). Filer er en af ​​de eneste ting delt og ikke klonet mellem forælder og barn. Hvis du læser 16 bytes som overordnet, flytter den læsemarkøren fremad på 16 bytes begge hos forældrene og hos barnet. Værst, hvis barn og forælder skriver bytes til samme fil på samme tid kan forældrenes bytes være blandet med bytes af barnet!

For at være klar, uden for STDIN, STDOUT og STDERR, vil du virkelig ikke dele nogen åbne filer med kloner.

For det tredje skal du være forsigtig med stikkontakter. Stikkontakter er delte også mellem forældre og børn. Det er nyttigt for at lytte til en port og derefter lade flere børnearbejdere være klar til at håndtere en ny klientforbindelse. Imidlertid, hvis du bruger det forkert, får du problemer.

For det fjerde, hvis du vil kalde fork () inden for en løkke, skal du gøre dette med ekstrem forsigtighed. Lad os tage denne kode:

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

Hvis du læser koden, kan du forvente, at den opretter 4 børn. Men det vil snarere skabe 16 børn. Det er fordi childs vil også udføre løkken, og så vil barn igen ringe til fork (). Når sløjfen er uendelig, kaldes den a gaffelbombe og er en af ​​måderne til at bremse et Linux-system så meget, at det ikke længere fungerer og har brug for en genstart. I en nøddeskal skal du huske på, at Clone Wars ikke kun er farligt i Star Wars!

Nu har du set, hvordan en simpel sløjfe kan gå galt, hvordan man bruger sløjfer med gaffel ()? Hvis du har brug for en løkke, skal du altid kontrollere gaffelens returværdi:

const int targetFork = 4;
pid_t forkResult;
int i = 0;
gøre
forkResult = fork ();
/ *… * /
i ++;
mens ((forkResult != 0 && forkResult != -1) && (i < targetFork));

Konklusion

Nu er det tid for dig at lave dine egne eksperimenter med fork ()! Prøv nye måder at optimere tiden på ved at udføre opgaver på tværs af flere CPU-kerner, eller udfør baggrundsbehandling, mens du venter på at læse en fil!

Tøv ikke med at læse manualsiderne via man-kommandoen. Du vil lære om, hvordan fork () fungerer nøjagtigt, hvilke fejl du kan få osv. Og nyd samtidig!

5 bedste arkadespil til Linux
I dag er computere seriøse maskiner, der bruges til spil. Hvis du ikke kan få den nye høje score, ved du hvad jeg mener. I dette indlæg vil du kende n...
Kamp om Wesnoth 1.13.6 Udvikling frigivet
Kamp om Wesnoth 1.13.6 udgivet i sidste måned, er den sjette udviklingsudgivelse i 1.13.x-serien, og den leverer en række forbedringer, især til bruge...
Sådan installeres League Of Legends på Ubuntu 14.04
Hvis du er fan af League of Legends, er dette en mulighed for dig at prøvekøre League of Legends. Bemærk, at LOL understøttes på PlayOnLinux, hvis du ...