C programavimas

Jūsų pirmoji C programa, naudojant šakės sistemos skambutį

Jūsų pirmoji C programa, naudojant šakės sistemos skambutį
Pagal numatytuosius nustatymus C programos neturi sutapimo ar lygiagretumo, vienu metu įvyksta tik viena užduotis, kiekviena kodo eilutė skaitoma nuosekliai. Tačiau kartais turite perskaityti failą arba - net blogiausia - lizdą, prijungtą prie nuotolinio kompiuterio, o kompiuteriui tai užtrunka tikrai ilgai. Paprastai tai užtrunka mažiau nei sekundę, tačiau atminkite, kad vienas procesoriaus branduolys gali įvykdyti 1 arba 2 milijardus instrukcijų per tą laiką.

Taigi, kaip geras kūrėjas, jums kils pagunda palaukti C programą padaryti ką nors naudingesnio laukiant. Štai kur jūsų programai naudingas lygiagretumas - ir daro jūsų kompiuterį nelaimingu, nes jis turi veikti daugiau.

Čia aš jums parodysiu „Linux“ šakių sistemos skambutį, kuris yra vienas saugiausių būdų vienu metu programuoti.

Programavimas vienu metu gali būti nesaugus?

Taip jis gali. Pavyzdžiui, yra ir kitas skambinimo būdas daugiasluoksnis. Ji turi pranašumą būti lengvesnė, bet gali tikrai suklysti, jei jį naudojate neteisingai. Jei jūsų programa per klaidą perskaito kintamąjį ir parašo tas pats kintamasis tuo pačiu metu jūsų programa taps nenuosekli ir jos beveik neaptiksite - vienas blogiausių kūrėjų košmarų.

Kaip pamatysite žemiau, šakutė nukopijuoja atmintį, todėl neįmanoma turėti tokių problemų su kintamaisiais. Be to, šakutė daro nepriklausomą procesą kiekvienai gretutinei užduočiai. Dėl šių saugumo priemonių maždaug 5 kartus lėčiau pradėti naują lygiagrečią užduotį naudojant šakę, nei naudojant daugialypį sriegį. Kaip matote, tai nėra daug naudos, kurią ji teikia.

Pakanka paaiškinimų, atėjo laikas išbandyti savo pirmąją C programą naudodamiesi šakės skambučiu.

„Linux“ šakės pavyzdys

Štai kodas:

# įtraukti
# įtraukti
# įtraukti
# įtraukti
# įtraukti
int main ()
pid_t forkStatus;
šakutėStatus = šakutė ();
/ * Vaikas ... * /
jei (forkStatus == 0)
printf ("Vaikas veikia, apdoroja.\ n ");
miegas (5);
printf ("Vaikas baigtas, išeina.\ n ");
/ * Tėvas… * /
else if (forkStatus != -1)
printf ("Tėvas laukia ... \ n");
palaukti (NULL);
printf ("Tėvas išeina ... \ n");
Kitas
perror ("Klaida skambinant šakės funkcijai");

grąžinti 0;

Kviečiu išbandyti, sukompiliuoti ir įvykdyti aukščiau esantį kodą, bet jei norite pamatyti, kaip atrodys išvestis, ir esate per daug „tingus“ jį kompiliuoti - galų gale jūs esate pavargęs kūrėjas, kuris visą dieną sudarydavote C programas - žemiau galite rasti C programos išvestį kartu su komanda, kurią naudojau jai kompiliuoti:

$ gcc -std = c89 -Wpedantic -Wall forkSleep.c -o šakutė Miegas -O2
$ ./ šakutė miega
Tėvas laukia ..
Vaikas bėga, apdoroja.
Vaikas baigtas, išeinantis.
Tėvas išeina ..

Prašau nebijoti, jei išvestis nėra 100% identiška mano pirmiau pateiktai. Atminkite, kad dalykų vykdymas tuo pačiu metu reiškia, kad užduotys baigiasi ne savo tvarka, nėra iš anksto nustatyto užsakymo. Šiame pavyzdyje galite pamatyti, kad vaikas veikia prieš tai tėvas laukia, ir tame nėra nieko blogo. Paprastai užsakymas priklauso nuo branduolio versijos, procesoriaus branduolių skaičiaus, šiuo metu jūsų kompiuteryje veikiančių programų ir kt.

Gerai, dabar grįžkite prie kodo. Prieš eilutę su šakute () ši C programa yra visiškai normali: vienu metu vykdoma 1 eilutė, yra tik vienas šios programos procesas (jei iki šakutės buvo nedidelis vėlavimas, tai galite patvirtinti savo užduočių tvarkytuvėje).

Po šakute () dabar yra 2 procesai, kurie gali veikti lygiagrečiai. Pirma, vyksta vaiko procesas. Šis procesas buvo sukurtas ant šakės (). Šis antrinis procesas yra ypatingas: jis neįvykdė nė vienos kodo eilutės virš eilutės su šakute (). Užuot ieškojęs pagrindinės funkcijos, jis greičiau paleis šakės () eilutę.

Ką apie kintamuosius, deklaruotus prieš šakę?

Na, „Linux“ šakutė () yra įdomi, nes protingai atsako į šį klausimą. Kintamieji ir, tiesą sakant, visa C programų atmintis nukopijuojama į vaiko procesą.

Keliais žodžiais leiskite man apibrėžti, kas daro šakę: ji sukuria a klonas proceso vadinimu. 2 procesai yra beveik identiški: visi kintamieji turės tas pačias reikšmes ir abu procesai vykdys eilutę iškart po šakės (). Tačiau po klonavimo proceso, jie yra atskirti. Jei atnaujinsite kintamąjį per vieną procesą, kitas procesas nebus atnaujinti jo kintamąjį. Tai tikrai klonas, kopija, procesai beveik nieko nedalija. Tai tikrai naudinga: galite paruošti daug duomenų, tada šakoti () ir naudoti tuos duomenis visuose klonuose.

Atskyrimas prasideda, kai šakutė () grąžina vertę. Originalus procesas (jis vadinamas tėvų procesą) gaus klonuoto proceso ID. Kita vertus, klonuotas procesas (šis vadinamas vaiko procesas) gaus 0 skaičių. Dabar turėtumėte pradėti suprasti, kodėl sakinius įdėjau if / else if po šakės () eilute. Naudodami grąžinimo vertę, galite nurodyti vaikui elgtis kitaip nei tėvai - ir patikėk, tai naudinga.

Iš vienos pusės, aukščiau pateiktame pavyzdiniame kode, vaikas atlieka užduotį, kuri trunka 5 sekundes, ir atsispausdina pranešimą. Norėdami imituoti procesą, kuris trunka ilgai, aš naudoju miego funkciją. Tada vaikas sėkmingai išeina.

Kitoje pusėje tėvai išspausdina pranešimą, palaukia, kol vaikas išeis, ir galiausiai išspausdins kitą pranešimą. Svarbu tai, kad tėvai laukia savo vaiko. Kaip pavyzdys, tėvai daugiausia laiko laukia savo vaiko. Bet aš galėjau nurodyti tėvams atlikti bet kokias ilgai trunkančias užduotis, prieš liepdamas palaukti. Tokiu būdu ji būtų atlikusi naudingų užduočių, užuot laukusi - galų gale, todėl mes naudojame šakutė (), Nr?

Tačiau, kaip sakiau aukščiau, tai tikrai svarbu tėvas laukia savo vaikų. Ir tai svarbu dėl to zombių procesai.

Kaip svarbu laukti

Tėvai paprastai nori sužinoti, ar vaikai baigė jų apdorojimą. Pavyzdžiui, norite vykdyti užduotis lygiagrečiai, bet tu tikrai nenori vienas iš tėvų turi išeiti, kol dar nėra vaikų, nes jei taip nutiktų, apvalkalas grąžintų raginimą, kol vaikai dar nebaigė - kas keista.

Laukimo funkcija leidžia laukti, kol vienas iš vaiko procesų bus nutrauktas. Jei tėvas skambina 10 kartų šakute (), jis taip pat turės skambinti 10 kartų palaukti (), po vieną kartą kiekvienam vaikui sukurta.

Bet kas nutiks, jei tėvai iškvies laukimo funkciją, kai visi vaikai turės jau išėjo? Štai kur reikalingi zombių procesai.

Kai vaikas išeina prieš laukiant tėvų skambučių (), „Linux“ branduolys leis vaikui išeiti bet jis pasiliks bilietą sakydamas, kad vaikas išėjo. Tada, kai tėvai skambins laukti (), jis ras bilietą, ištrins tą bilietą ir laukimo () funkcija grįš nedelsiant nes žino, kad tėvas turi žinoti, kada vaikas baigs darbą. Šis bilietas vadinamas a zombių procesas.

Štai kodėl svarbu, kad tėvų skambučiai lauktų (): jei to nepadaro, zombių procesai lieka atmintyje ir „Linux“ branduolyje negali išsaugoti daugybę zombių procesų atmintyje. Pasiekus ribą, jūsų kompiuteris, tnesugeba sukurti jokio naujo proceso ir taip jūs būsite labai blogos formos: net norint nužudyti procesą, gali tekti sukurti naują procesą. Pvz., Jei norite atidaryti užduočių tvarkytuvą, kad užmuštumėte procesą, negalite, nes užduočių tvarkyklei reikės naujo proceso. Net blogiausia, tu negali nužudyti zombių procesą.

Štai kodėl laukimas yra svarbus: jis leidžia branduolį Išvalyti vaiko procesą, o ne kaupti užbaigtų procesų sąrašą. O kas, jei tėvas išeis niekada nepaskambinęs laukti()?

Laimei, kai tėvas yra nutrauktas, niekas kitas negali skambinti laukti () šiems vaikams, todėl yra jokios priežasties išlaikyti šiuos zombių procesus. Todėl, kai tėvai išeina, visi likę zombių procesai susieta su šiuo tėvu yra pašalinami. Zombių procesai yra tikrai naudinga tik leidžiant tėvų procesams sužinoti, kad vaikas nutraukiamas prieš tėvams iškviečiant laukimą ().

Dabar galbūt norėsite žinoti kai kurias saugos priemones, kad būtų galima kuo geriau naudoti šakę be jokių problemų.

Paprastos taisyklės, kad šakutė veiktų pagal paskirtį

Pirma, jei žinote daugialypį gijimą, prašome neužtikrinti programos naudojant gijas. Tiesą sakant, venkite maišyti kelių lygiagrečių technologijų. šakutė daro darbą įprastose C programose, ji ketina klonuoti tik vieną lygiagrečią užduotį, o ne daugiau.

Antra, venkite atidaryti ar atidaryti failus prieš šakutę (). Failai yra vienas vienintelis dalykas pasidalino ir ne klonuoti tarp tėvų ir vaiko. Jei perskaitysite 16 baitų iš tėvų, jis perkels skaitymo žymeklį į priekį nuo 16 baitų tiek tėvuose ir vaiko. Blogiausias, jei vaikas ir tėvai rašo baitus tas pats failas tuo pačiu metu tėvų baitai gali būti sumaišytas su vaiko baitais!

Kad būtų aišku, už STDIN, STDOUT ir STDERR ribų jūs tikrai nenorite dalintis jokiais atvirais failais su klonais.

Trečia, būkite atsargūs dėl lizdų. Lizdai yra taip pat pasidalino tarp tėvų ir vaikų. Tai naudinga norint išklausyti uostą ir leisti keliems darbuotojams vaikams pasiruošti tvarkyti naują kliento ryšį. Tačiau, neteisingai jį naudodami pateksite į bėdą.

Ketvirta, jei norite iškviesti šakę () cikle, atlikite tai su ypatinga priežiūra. Paimkime šį kodą:

/ * NENORINKITE ŠIO * /
const int targetFork = 4;
pid_t forkResult
 
už (int i = 0; i < targetFork; i++)
šakutėRezultatas = šakutė ();
/ *… * /
 

Jei perskaitysite kodą, galite tikėtis, kad jis sukurs 4 vaikus. Bet tai labiau sukurs 16 vaikų. Taip yra todėl, kad vaikai tai padarys taip pat įvykdys kilpą, todėl vaikas savo ruožtu iškvies šakutę (). Kai kilpa yra begalinė, ji vadinama a šakės bomba ir yra vienas iš būdų sulėtinti „Linux“ sistemą tiek, kad nebeveikia ir reikės iš naujo paleisti. Trumpai tariant, nepamirškite, kad „Klonų karai“ yra pavojingi ne tik „Žvaigždžių karuose“!

Dabar jūs pamatėte, kaip paprasta klaida gali suklysti, kaip naudoti kilpas su šakute ()? Jei jums reikia kilpos, visada patikrinkite šakės grąžinimo vertę:

const int targetFork = 4;
pid_t forkResult;
int i = 0;
padaryti
šakutėRezultatas = šakutė ();
/ *… * /
i ++;
((forkResult != 0 && šakutės rezultatas != -1) && (t < targetFork));

Išvada

Dabar pats laikas atlikti savo eksperimentus su šakute ()! Išbandykite naujus laiko optimizavimo būdus atlikdami užduotis keliuose procesoriaus branduoliuose arba atlikdami fono apdorojimą, kol laukiate failo skaitymo!

Nedvejodami perskaitykite vadovo puslapius naudodami komandą vyras. Sužinosite, kaip tiksliai veikia šakutė (), kokių klaidų galite gauti ir pan. Ir mėgaukitės tuo pačiu metu!

„Microsoft Sculpt Touch“ belaidžio pelės apžvalga
Neseniai skaičiau apie „Microsoft Sculpt Touch“ belaidę pelę ir nusprendė ją nusipirkti. Kurį laiką naudojęs, nusprendžiau pasidalinti savo patirtimi....
„AppyMouse“ ekrano „Trackpad“ ir pelės žymeklis, skirtas „Windows“ tabletėms
Planšetinių kompiuterių vartotojai dažnai praleidžia pelės žymeklį, ypač kai įprasta naudoti nešiojamus kompiuterius. Jutiklinio ekrano išmanieji tele...
Vidutinis pelės mygtukas neveikia sistemoje „Windows 10“
The vidurinis pelės mygtukas padeda naršyti ilgus tinklalapius ir ekranus, kuriuose yra daug duomenų. Jei tai sustos, gerai, jūs naudosite klaviatūrą ...