C Programmering

Ditt första C-program med gaffelsystemsamtal

Ditt första C-program med gaffelsystemsamtal
Som standard har C-program ingen samtidighet eller parallellitet, bara en uppgift sker i taget, varje kodrad läses sekventiellt. Men ibland måste du läsa en fil eller - även värst - ett uttag anslutet till en fjärrdator och det tar verkligen lång tid för en dator. Det tar i allmänhet mindre än en sekund men kom ihåg att en enda CPU-kärna kan kör 1 eller 2 miljarder instruktioner under den tiden.

Så, som en bra utvecklare, du kommer att frestas att instruera ditt C-program att göra något mer användbart medan du väntar. Det är här samtidigt programmering är här för din räddning - och gör din dator olycklig eftersom den måste fungera mer.

Här visar jag dig Linux-gaffelsystemsamtalet, ett av det säkraste sättet att göra samtidig programmering.

Samtidig programmering kan vara osäker?

Ja, det kan det. Till exempel finns det också ett annat sätt att ringa multithreading. Det har fördelen att vara lättare men det kan verkligen gå fel om du använder det fel. Om ditt program av misstag läser en variabel och skriver till samma variabel samtidigt blir ditt program osammanhängande och det är nästan omöjligt att upptäcka - en av de värsta mardrömmarna.

Som du kommer att se nedan kopierar gaffeln minnet så det är inte möjligt att ha sådana problem med variabler. Gaffel gör också en oberoende process för varje samtidig uppgift. På grund av dessa säkerhetsåtgärder är det ungefär fem gånger långsammare att starta en ny samtidig uppgift med gaffel än med multitrådning. Som du kan se är det inte mycket för de fördelar det ger.

Nu, tillräckligt med förklaringar, är det dags att testa ditt första C-program med hjälp av fork call.

Exempel på Linux-gaffel

Här är koden:

#omfatta
#omfatta
#omfatta
#omfatta
#omfatta
int main ()
pid_t forkStatus;
forkStatus = gaffel ();
/* Barn… */
om (forkStatus == 0)
printf ("Barnet är igång, bearbetar.\ n ");
sömn (5);
printf ("Child is done, exiting.\ n ");
/ * Förälder ... * /
annars om (forkStatus != -1)
printf ("Förälder väntar ... \ n");
vänta (NULL);
printf ("Förälder går ut ... \ n");
annat
perror ("Fel vid anrop till gaffelfunktionen");

returnera 0;

Jag uppmanar dig att testa, kompilera och köra koden ovan men om du vill se hur produktionen skulle se ut och du är för "lat" för att kompilera den - trots allt är du kanske en trött utvecklare som sammanställt C-program hela dagen - du hittar utdata från C-programmet nedan tillsammans med kommandot som jag använde för att kompilera det:

$ gcc -std = c89 -Wpedantic -Wall forkSleep.c-o gaffel Sov-O2
$ ./ forkSleep
Förälder väntar ..
Barnet springer, bearbetar.
Barnet är klart, går ut.
Förälder går ut ..

Var inte rädd om produktionen inte är 100% identisk med min produktion ovan. Kom ihåg att att köra saker samtidigt innebär att uppgifter går ut ur ordning, det finns ingen fördefinierad beställning. I det här exemplet kan du se att barnet springer innan förälder väntar och det är inget fel med det. I allmänhet beror beställningen på kärnversionen, antalet CPU-kärnor, de program som för närvarande körs på din dator, etc.

OK, kom nu tillbaka till koden. Innan linjen med gaffel () är detta C-program helt normalt: 1 rad körs i taget, det finns bara en process för detta program (om det var en liten fördröjning före gaffeln, kan du bekräfta det i din uppgiftshanterare).

Efter gaffeln () finns det nu två processer som kan köras parallellt. Först finns det en barnprocess. Denna process är den som har skapats vid gaffel (). Denna underordnade process är speciell: den har inte kört någon av kodraderna ovanför raden med gaffel (). Istället för att leta efter huvudfunktionen kommer den snarare att köra gaffel () -linjen.

Hur är det med de variabler som deklarerats före gaffel??

Tja, Linux fork () är intressant eftersom det svarar smart på den här frågan. Variabler och i själva verket allt minne i C-program kopieras till barnprocessen.

Låt mig definiera vad som gör gaffel i några ord: det skapar en klona av processen som kallar det. De två processerna är nästan identiska: alla variabler innehåller samma värden och båda processerna kör linjen precis efter gaffeln (). Men efter kloningsprocessen, de är separerade. Om du uppdaterar en variabel i en process, den andra processen vana har sin variabel uppdaterad. Det är verkligen en klon, en kopia, processerna delar nästan ingenting. Det är verkligen användbart: du kan förbereda mycket data och sedan gaffla () och använda den informationen i alla kloner.

Separationen börjar när gaffel () returnerar ett värde. Den ursprungliga processen (den heter föräldraprocessen) får process-ID för den klonade processen. På andra sidan, den klonade processen (den här kallas barnprocessen) får 0-numret. Nu bör du börja förstå varför jag har lagt if / else if uttalanden efter fork () raden. Med returvärdet kan du instruera barnet att göra något annorlunda än vad föräldern gör - och tro mig, det är användbart.

På ena sidan, i exempelkoden ovan, gör barnet en uppgift som tar 5 sekunder och skriver ut ett meddelande. För att imitera en process som tar lång tid använder jag sömnfunktionen. Därefter går barnet framgångsrikt.

På andra sidan skriver föräldern ut ett meddelande, vänta tills barnet går ut och slutligen skriver ut ett nytt meddelande. Det faktum att föräldern väntar på sitt barn är viktigt. Som ett exempel väntar föräldern större delen av den här tiden på att vänta på sitt barn. Men jag kunde ha instruerat föräldern att göra någon form av långvariga uppgifter innan jag ber den vänta. På detta sätt skulle det ha gjort användbara uppgifter istället för att vänta - trots allt är det därför vi använder gaffel (), nej?

Men som jag sa ovan är det verkligen viktigt att förälder väntar på sina barn. Och det är viktigt på grund av zombie processer.

Hur väntar är viktigt

Föräldrar vill i allmänhet veta om barn har avslutat behandlingen. Till exempel vill du köra uppgifter parallellt men du vill verkligen inte föräldern att lämna innan barnen är färdiga, för om det hände skulle shell ge tillbaka en uppmaning medan barnen inte är färdiga ännu - vilket är konstigt.

Med väntefunktionen kan du vänta tills en av barnprocesserna avslutas. Om en förälder ringer 10 gånger fork (), måste den också ringa 10 gånger vänta (), en gång för varje barn skapad.

Men vad händer om föräldrar ringer vänta-funktionen medan alla barn har redan lämnade? Det är där zombieprocesser behövs.

När ett barn avslutar innan föräldrarna väntar (), kommer Linux-kärnan att låta barnet avsluta men det kommer att hålla en biljett berätta för barnet har gått ut. Sedan, när föräldern ringer vänta (), kommer den att hitta biljetten, radera den biljetten och vänta () -funktionen kommer tillbaka omedelbart eftersom det vet att föräldern behöver veta när barnet är klart. Denna biljett kallas a zombie-processen.

Det är därför det är viktigt att föräldrasamtal väntar (): om det inte gör det förblir zombieprocesser i minnet och Linux-kärnan kan inte hålla många zombieprocesser i minnet. När gränsen har uppnåtts, kommer din dator ikan inte skapa någon ny process och så kommer du att vara i en mycket dålig form: även för att döda en process kan du behöva skapa en ny process för det. Om du till exempel vill öppna din uppgiftshanterare för att döda en process kan du inte, eftersom din uppgiftshanterare behöver en ny process. Till och med värst, du kan inte döda en zombieprocess.

Det är därför det är viktigt att ringa vänta: det tillåter kärnan städa barnprocessen istället för att fortsätta stapla upp med en lista över avslutade processer. Och tänk om föräldern går ut utan att någonsin ringa vänta()?

Lyckligtvis, när föräldern avslutas, kan ingen annan ringa vänta () på dessa barn, så det finns ingen anledning för att behålla dessa zombieprocesser. Därför när en förälder går ut, alla återstående zombieprocesser kopplad till den här föräldern tas bort. Zombie processer är verkligen bara användbart för att föräldraprocesser ska kunna upptäcka att ett barn avslutades innan föräldern kallade vänta ().

Nu kanske du föredrar att känna till några säkerhetsåtgärder för att ge dig bästa möjliga användning av gaffel utan problem.

Enkla regler för att gaffeln ska fungera som avsett

Först, om du känner till multitrådning, snälla inte gaffla ett program med trådar. Undvik i allmänhet att blanda flera samtidiga tekniker. fork antar att arbeta i normala C-program, den har bara för avsikt att klona en parallell uppgift, inte mer.

För det andra, undvik att öppna eller öppna filer innan gaffel (). Filer är en av det enda delad och inte klonade mellan förälder och barn. Om du läser 16 byte som förälder flyttar den läspekaren framåt 16 byte både hos föräldern och hos barnet. Värst, om barn och förälder skriver byte till samma fil samtidigt kan bytes föräldrar vara blandad med byte av barnet!

För att vara tydlig, utanför STDIN, STDOUT och STDERR vill du verkligen inte dela några öppna filer med kloner.

För det tredje, var försiktig med uttag. Uttag är delade också mellan förälder och barn. Det är användbart för att lyssna på en port och sedan låta flera barnarbetare vara redo att hantera en ny klientanslutning. i alla fall, om du använder det fel kommer du att få problem.

För det fjärde, om du vill ringa fork () i en slinga, gör detta med extrem försiktighet. Låt oss ta den här koden:

/ * SAMMANFATTA INTE DETTA * /
const int targetFork = 4;
pid_t forkResult
 
för (int i = 0; i < targetFork; i++)
forkResult = gaffel ();
/ *… * /
 

Om du läser koden kan du förvänta dig att det skapar fyra barn. Men det kommer snarare att skapa 16 barn. Det beror på att barn kommer att göra det också köra slingan och så kommer barn i sin tur att ringa gaffel (). När slingan är oändlig kallas den a gaffelbomb och är ett av sätten att sakta ner ett Linux-system så mycket att det inte längre fungerar och behöver en omstart. I ett nötskal, kom ihåg att Clone Wars inte bara är farligt i Star Wars!

Nu har du sett hur en enkel slinga kan gå fel, hur man använder öglor med gaffel ()? Om du behöver en slinga, kontrollera alltid gaffelns returvärde:

const int targetFork = 4;
pid_t forkResult;
int i = 0;
gör
forkResult = gaffel ();
/ *… * /
i ++;
medan ((forkResult != 0 && forkResult != -1) && (i < targetFork));

Slutsats

Nu är det dags för dig att göra dina egna experiment med gaffel ()! Prova nya sätt att optimera tiden genom att utföra uppgifter över flera CPU-kärnor eller gör bakgrundsbehandling medan du väntar på att läsa en fil!

Tveka inte att läsa manualsidorna via man-kommandot. Du kommer att lära dig hur fork () fungerar exakt, vilka fel du kan få osv. Och njut av samtidighet!

Hur man använder GameConqueror Cheat Engine i Linux
Artikeln täcker en guide om hur du använder GameConqueror-fuskmotorn i Linux. Många användare som spelar spel på Windows använder ofta applikationen "...
Bästa spelkonsolemulatorer för Linux
Den här artikeln listar populära spelkonsolemuleringsprogram som finns tillgängliga för Linux. Emulation är ett mjukvarukompatibilitetsskikt som emule...
Bästa Linux Distros för spel 2021
Linux-operativsystemet har kommit långt från sitt ursprungliga, enkla, serverbaserade utseende. Detta operativsystem har förbättrats enormt de senaste...