C Programmēšana

Jūsu pirmā C programma, izmantojot dakšu sistēmas izsaukumu

Jūsu pirmā C programma, izmantojot dakšu sistēmas izsaukumu
Pēc noklusējuma C programmām nav vienlaicīguma vai paralēlisma, vienlaikus notiek tikai viens uzdevums, katra koda rinda tiek lasīta secīgi. Bet dažreiz jums ir jālasa fails vai - pat sliktākais - kontaktligzda, kas savienota ar attālo datoru, un tas datoram prasa patiešām ilgu laiku. Tas parasti aizņem mazāk nekā sekundi, bet atcerieties, ka viens CPU kodols var izpildīt 1 vai 2 miljardus instrukcijas tajā laikā.

Tātad, kā labs izstrādātājs, jums radīsies kārdinājums uzdot savai C programmai gaidīšanas laikā izdarīt kaut ko noderīgāku. Jūsu glābšanai šeit ir paredzēta vienlaicīga programmēšana - un padara jūsu datoru nelaimīgu, jo tam ir jāstrādā vairāk.

Šeit es jums parādīšu Linux dakšu sistēmas izsaukumu, kas ir viens no drošākajiem vienlaicīgas programmēšanas veidiem.

Vienlaicīga programmēšana var būt nedroša?

Jā, tā var. Piemēram, ir arī cits zvana veids daudzsavienojums. Tā priekšrocība ir vieglāka, bet tā var tiešām noiet greizi, ja to izmantojat nepareizi. Ja jūsu programma kļūdas dēļ nolasa mainīgo un raksta tas pats mainīgais tajā pašā laikā jūsu programma kļūs nesakarīga, un to gandrīz nevar noteikt - viens no sliktākajiem izstrādātāju murgiem.

Kā redzēsit zemāk, dakša kopē atmiņu, tāpēc nav iespējams radīt šādas problēmas ar mainīgajiem. Arī dakša padara neatkarīgu procesu katram vienlaicīgam uzdevumam. Sakarā ar šiem drošības pasākumiem ir aptuveni 5x lēnāk uzsākt jaunu vienlaicīgu uzdevumu, izmantojot dakšiņu, nekā ar daudzsavienojumu. Kā redzat, tas nav daudz par ieguvumiem, ko tas dod.

Pietiek ar paskaidrojumiem, ir pienācis laiks pārbaudīt savu pirmo C programmu, izmantojot dakšas zvanu.

Linux dakšas piemērs

Šeit ir kods:

# iekļaut
# iekļaut
# iekļaut
# iekļaut
# iekļaut
int main ()
pid_t dakšaStatus;
dakšaStatus = dakša ();
/* Bērns… */
ja (forkStatus == 0)
printf ("Bērns darbojas, apstrādā.\ n ");
gulēt (5);
printf ("Bērns ir pabeigts, izejot no.\ n ");
/ * Vecāks… * /
else if (forkStatus != -1)
printf ("Vecāks gaida ... \ n");
pagaidiet (NULL);
printf ("Vecāks iziet ... \ n");
cits
perror ("Kļūda, izsaucot dakšas funkciju");

atgriešanās 0;

Es aicinu jūs pārbaudīt, apkopot un izpildīt iepriekš minēto kodu, bet, ja vēlaties redzēt, kā izeja izskatīsies, un esat pārāk slinks, lai to apkopotu - galu galā, jūs varbūt esat noguris izstrādātājs, kurš visas dienas garumā sastādīja C programmas - zemāk varat atrast C programmas izvadi kopā ar komandu, kuru izmantoju, lai to kompilētu:

$ gcc -std = c89 -Wpedantic -Sienas dakšaSleep.c -o dakšaGulēt -O2
$ ./ dakšaGulēt
Vecāks gaida ..
Bērns skrien, apstrādā.
Bērns ir pabeigts, izejot.
Vecāks iziet ..

Lūdzu, nebaidieties, ja produkcija nav 100% identiska manai iepriekšējai izejai. Atcerieties, ka visu lietu vienlaicīga izpildīšana nozīmē, ka uzdevumi ir nederīgi, nav iepriekš noteiktas pasūtīšanas. Šajā piemērā jūs varat redzēt, ka bērns darbojas pirms vecāks gaida, un tur nav nekā slikta. Parasti pasūtīšana ir atkarīga no kodola versijas, CPU kodolu skaita, programmām, kas pašlaik darbojas jūsu datorā utt.

Labi, tagad atgriezieties pie koda. Pirms rindas ar dakšiņu () šī C programma ir pilnīgi normāla: vienlaikus tiek izpildīta 1 rinda, šai programmai ir tikai viens process (ja pirms dakšas bija neliela kavēšanās, to varat apstiprināt uzdevumu pārvaldniekā).

Pēc dakšas () tagad ir 2 procesi, kas var darboties paralēli. Pirmkārt, notiek bērna process. Šis process ir tas, kas ir izveidots uz dakšas (). Šis bērnu process ir īpašs: tas nav izpildījis nevienu koda rindiņu virs līnijas ar dakšiņu (). Tā vietā, lai meklētu galveno funkciju, tā drīzāk vadīs dakšas () līniju.

Kas par mainīgajiem, kas deklarēti pirms dakšas?

Nu, Linux dakša () ir interesanta, jo tā gudri atbild uz šo jautājumu. Mainīgie un faktiski visa C programmu atmiņa tiek kopēta bērna procesā.

Ļaujiet man dažiem vārdiem definēt, kas notiek dakša: tas rada klons procesa saukšana. Šie divi procesi ir gandrīz identiski: visiem mainīgajiem būs vienādas vērtības, un abi procesi izpildīs līniju tūlīt pēc dakšas (). Tomēr pēc klonēšanas procesa, tie ir atdalīti. Ja maināt mainīgo vienā procesā, otru procesu nebūs atjaunināt tā mainīgo. Tas tiešām ir klons, kopija, procesos gandrīz nekas netiek dalīts. Tas ir patiešām noderīgi: jūs varat sagatavot daudz datu un pēc tam atdalīt () un izmantot šos datus visos klonos.

Atdalīšana sākas, kad fork () atgriež vērtību. Sākotnējais process (to sauc vecāku process) iegūs klonētā procesa ID. No otras puses, klonētais process (šo sauc par bērna process) iegūs 0 skaitli. Tagad jums vajadzētu sākt saprast, kāpēc es ievietoju if / else if paziņojumus aiz dakšas () rindas. Izmantojot atgriešanās vērtību, jūs varat uzdot bērnam darīt kaut ko citu, nekā vecāks - un tici man, tas ir noderīgi.

Vienā pusē iepriekšminētajā koda piemērā bērns veic uzdevumu, kas aizņem 5 sekundes, un izdrukā ziņojumu. Lai atdarinātu procesu, kas aizņem ilgu laiku, es izmantoju miega funkciju. Tad bērns veiksmīgi iziet.

No otras puses vecāki izdrukā ziņojumu, pagaidiet, līdz bērns iziet, un beidzot izdrukā citu ziņojumu. Fakts, ka vecāki gaida savu bērnu, ir svarīgs. Kā piemēru, vecāki lielāko daļu laika gaida sava bērna gaidīšanu. Bet es varētu būt norādījis vecākam veikt jebkādus ilgstošus uzdevumus, pirms liku viņam gaidīt. Tādā veidā tas būtu izdarījis noderīgus uzdevumus, nevis gaidīšanu - galu galā tāpēc mēs to izmantojam dakša (), Nr?

Tomēr, kā jau teicu iepriekš, tas ir patiešām svarīgi vecāks gaida savus bērnus. Un tas ir svarīgi dēļ zombiju procesi.

Cik svarīgi ir gaidīt

Vecāki parasti vēlas uzzināt, vai bērni ir pabeiguši apstrādi. Piemēram, jūs vēlaties izpildīt uzdevumus paralēli, bet jūs noteikti nevēlaties vecākam iziet, pirms bērni tiek darīti, jo, ja tas notiktu, čaula atdotu brīdinājumu, kamēr bērni vēl nav pabeiguši - kas ir dīvaini.

Gaidīšanas funkcija ļauj gaidīt, kamēr kāds no bērna procesiem tiek pārtraukts. Ja vecāks zvana 10 reizes ar dakšiņu (), tam būs jāzvana arī 10 reizes, kamēr jāgaida (), reizi katram bērnam izveidots.

Bet kas notiek, ja vecāku zvanu gaidīšanas funkcija tiek izmantota visiem bērniem jau iziet? Tieši tur ir nepieciešami zombiju procesi.

Kad bērns iziet pirms vecāku zvanu gaidīšanas (), Linux kodols ļaus bērnam iziet bet tā paturēs biļeti stāsta, ka bērns ir izgājis. Tad, kad vecāki zvana gaidīt (), tas atradīs biļeti, izdzēsīs šo biļeti un atgriezīsies funkcija wait () nekavējoties jo tā zina, ka vecākiem ir jāzina, kad bērns ir pabeidzis darbu. Šo biļeti sauc par a zombiju process.

Tāpēc ir svarīgi, lai vecāku zvani wait (): ja tas nenotiek, zombiju procesi paliek atmiņā un Linux kodolā nevar saglabāt daudzus zombiju procesus atmiņā. Kad limits ir sasniegts, jūsu dators iNevar izveidot jaunu procesu un tāpēc jūs būsiet a ļoti slikta forma: pat Lai nogalinātu procesu, jums, iespējams, būs jāizveido jauns process. Piemēram, ja vēlaties atvērt uzdevumu pārvaldnieku, lai nogalinātu procesu, nevarat, jo uzdevumu pārvaldniekam būs nepieciešams jauns process. Pat sliktākais, tu nevari nogalināt zombiju procesu.

Tāpēc zvana gaidīšana ir svarīga: tā ļauj kodolu satīrīt bērna procesu, tā vietā, lai turpinātu sakraut izbeigto procesu sarakstu. Un ja vecāks iziet, nekad nezvanot pagaidiet ()?

Par laimi, tā kā vecāks tiek pārtraukts, neviens cits nevar piezvanīt uz gaidīšanu () šiem bērniem, tāpēc tur ir nav iemesla saglabāt šos zombiju procesus. Tāpēc, kad vecāks iziet, visas atlikušās zombiju procesi saistīts ar šo vecāku tiek noņemtas. Zombiju procesi ir tiešām noderīgi tikai, lai ļautu vecāku procesiem konstatēt, ka bērns pārtrauca darbību pirms vecāku izsaukšanas uz gaidīšanu ().

Tagad jūs, iespējams, vēlēsities zināt dažus drošības pasākumus, lai dakšu varētu bez problēmām izmantot vislabāk.

Vienkārši noteikumi, lai dakša darbotos kā paredzēts

Pirmkārt, ja jūs zināt daudzsavienojumu, lūdzu, netraucējiet programmu, izmantojot pavedienus. Patiesībā izvairieties no vairāku vienlaicīgu tehnoloģiju sajaukšanas. dakša pieņem darbu parastās C programmās, tā plāno klonēt tikai vienu paralēlu uzdevumu, nevis vairāk.

Otrkārt, izvairieties no failu atvēršanas vai atvēršanas pirms dakšas (). Faili ir viena vienīgā lieta dalīts un nē klonēts starp vecākiem un bērnu. Ja vecākos esat izlasījis 16 baitus, tas pārvietos lasīšanas kursoru uz priekšu no 16 baitiem gan vecākiem un bērnā. Sliktākais, ja bērns un vecāki raksta baitus pats fails tajā pašā laikā vecāku baiti var būt jaukts ar bērna baitiem!

Lai būtu skaidrs, ārpus STDIN, STDOUT un STDERR jūs patiešām nevēlaties koplietot nevienu atvērtu failu ar kloniem.

Treškārt, uzmanieties no kontaktligzdām. Kontaktligzdas ir arī dalījās starp vecākiem un bērniem. Tas ir noderīgi, lai klausītos portu un pēc tam ļautu vairākiem bērnu darbiniekiem būt gataviem apstrādāt jaunu klienta savienojumu. Tomēr, nepareizi lietojot, jūs nonāksiet nepatikšanās.

Ceturtkārt, ja vēlaties cilpā izsaukt dakšiņu (), dariet to ar ārkārtēja piesardzība. Paņemsim šo kodu:

/ * NESAKOPĒT ŠO * /
const int targetFork = 4;
pid_t forkResult
 
par (int i = 0; i < targetFork; i++)
forkResult = dakša ();
/ *… * /
 

Izlasot kodu, iespējams, ka tas izveidos 4 bērnus. Bet tas drīzāk radīs 16 bērni. Tas ir tāpēc, ka bērni to darīs arī izpildiet cilpu, un bērni, savukārt, izsauks dakšiņu (). Kad cilpa ir bezgalīga, to sauc par a dakšu bumba un ir viens no veidiem, kā palēnināt Linux sistēmu tik daudz, ka tas vairs nedarbojas un būs nepieciešama atsāknēšana. Īsāk sakot, paturiet prātā, ka Klonu kari ir bīstami ne tikai Zvaigžņu karos!

Tagad esat redzējis, kā vienkārša cilpa var noiet greizi, kā izmantot cilpas ar dakšiņu ()? Ja jums ir nepieciešama cilpa, vienmēr pārbaudiet dakšas atgriešanās vērtību:

const int targetFork = 4;
pid_t forkResult;
int i = 0;
darīt
forkResult = dakša ();
/ *… * /
i ++;
while ((forkResult != 0 && dakšas rezultāts != -1) && (i < targetFork));

Secinājums

Ir pienācis laiks pašiem veikt eksperimentus ar dakšiņu ()! Izmēģiniet jaunus laika optimizācijas veidus, veicot uzdevumus vairākos CPU kodolos vai veicot kādu fona apstrādi, kamēr gaidāt faila lasīšanu!

Nevilcinieties izlasīt rokasgrāmatas lapas, izmantojot komandu man. Jūs uzzināsiet par to, kā precīzi darbojas dakša (), kādas kļūdas var iegūt utt. Izbaudiet vienlaicīgumu!

HD Remastered spēles operētājsistēmai Linux, kurām nekad agrāk nebija Linux laidiena
Daudzi spēļu izstrādātāji un izdevēji nāk klajā ar veco spēļu HD remaster, lai pagarinātu franšīzes darbības laiku. Lūdzu, faniem, kas pieprasa saderī...
Kā izmantot AutoKey, lai automatizētu Linux spēles
AutoKey ir darbvirsmas automatizācijas lietderība operētājsistēmām Linux un X11, kas ieprogrammēta Python 3, GTK un Qt. Izmantojot skriptu un MACRO fu...
Kā parādīt FPS skaitītāju Linux spēlēs
Linux spēles ieguva lielu impulsu, kad Valve 2012. gadā paziņoja par Linux atbalstu Steam klientam un viņu spēlēm. Kopš tā laika daudzas AAA un indie ...