C Programmeerimine

Teie esimene C-programm kahvli süsteemikõne abil

Teie esimene C-programm kahvli süsteemikõne abil
Vaikimisi pole C-programmidel samaaegsust ega paralleelsust, korraga toimub ainult üks ülesanne, iga koodirida loetakse järjestikku. Kuid mõnikord peate lugema faili või - isegi kõige hullem - kaugarvutiga ühendatud pistikupesa ja see võtab arvuti jaoks tõesti palju aega. See võtab tavaliselt vähem kui sekundi, kuid pidage meeles, et üks protsessori tuum võib seda teha teostada 1 või 2 miljardit selle aja jooksul juhiseid.

Niisiis, hea arendajana, teil on kiusatus anda oma C-programmile käsk oodata midagi kasulikumat. Seal on teie päästmiseks siin samaaegsuse programmeerimine - ja muudab teie arvuti õnnetuks, kuna see peab rohkem töötama.

Siin ma näitan teile Linuxi kahvli süsteemi kõnet, mis on üks turvalisemaid viise samaaegse programmeerimise jaoks.

Samaaegne programmeerimine võib olla ohtlik?

Jah, saab küll. Näiteks on ka teine ​​viis helistamiseks mitmekeermeline. Selle eeliseks on kergem, kuid saab tõesti lähete valesti, kui kasutate seda valesti. Kui teie programm loeb ekslikult muutujat ja kirjutab sama muutuja samal ajal muutub teie programm sidusaks ja seda pole peaaegu võimalik tuvastada - üks halvemaid arendaja õudusunenägusid.

Nagu näete allpool, kopeerib fork mälu, nii et muutujate puhul pole selliseid probleeme võimalik. Samuti teeb fork iga samaaegse ülesande jaoks iseseisva protsessi. Nende turvameetmete tõttu on kahvli abil uue samaaegse toimingu käivitamine umbes viis korda aeglasem kui mitme niidiga. Nagu näete, pole see palju kasu, mida see toob.

Piisavalt selgitustest on aeg kahvlikõne abil oma esimest C-programmi testida.

Linuxi kahvli näide

Siin on kood:

# kaasata
# kaasata
# kaasata
# kaasata
# kaasata
int main ()
pid_t forkStatus;
forkStatus = kahvel ();
/ * Laps… * /
kui (forkStatus == 0)
printf ("Laps töötab, töötleb.\ n ");
uni (5);
printf ("Laps on valmis, väljub.\ n ");
/ * Vanem… * /
else if (forkStatus != -1)
printf ("Vanem ootab ... \ n");
ootama (NULL);
printf ("Vanem väljub ... \ n");
muu
perror ("Viga kahvlifunktsiooni kutsumisel");

tagastama 0;

Kutsun teid testima, kompileerima ja käivitama ülaltoodud koodi, kuid kui soovite näha, milline väljund välja näeks, ja olete selle kompileerimiseks liiga laisk - olete ju ehk väsinud arendaja, kes koostas terve päeva C programme - allpool leiate C-programmi väljundi koos käsuga, mida ma selle kompileerimiseks kasutasin:

$ gcc -std = c89 -Wpedantic -Wall forkSleep.c -o kahvelUnne -O2
$ ./ forkSleep
Vanem ootab ..
Laps töötab, töötleb.
Laps on valmis, väljub.
Vanem lahkub ..

Ärge kartke, kui väljund pole 100% identne minu ülaltoodud väljundiga. Pidage meeles, et asjade samaaegne käitamine tähendab, et ülesanded on järjest väljas, pole ette määratud järjestust. Selles näites näete, et laps töötab enne vanem ootab ja selles pole midagi halba. Üldiselt sõltub tellimine kerneli versioonist, protsessori tuumade arvust, teie arvutis praegu töötavatest programmidest jne.

OK, pöörduge nüüd koodi juurde tagasi. Enne rida fork () -ga on see C-programm täiesti normaalne: korraga käivitatakse 1 rida, selle programmi jaoks on ainult üks protsess (kui enne kahvlit oli väike viivitus, võite seda oma ülesannete halduris kinnitada).

Pärast kahvlit () on nüüd 2 paralleelselt töötavat protsessi. Esiteks toimub lapse protsess. See protsess on see, mis on loodud kahvlile (). See lapseprotsess on eriline: see pole hargiga () rea kohal ühtegi koodirida käivitanud. Põhifunktsiooni otsimise asemel käivitab see pigem rida fork ().

Aga muutujad, mis on deklareeritud enne kahvlit?

Noh, Linuxi kahvel () on huvitav, kuna see vastab sellele küsimusele arukalt. Muutujad ja tegelikult kogu C-programmide mälu kopeeritakse lapseprotsessi.

Lubage mul mõne sõnaga määratleda, mis kahvlit teeb: see loob a kloon protsessi kutsudes seda. Need kaks protsessi on peaaegu identsed: kõik muutujad sisaldavad samu väärtusi ja mõlemad protsessid täidavad rida kohe pärast kahvlit (). Pärast kloonimisprotsessi, nad on eraldatud. Kui värskendate muutujat ühes protsessis, siis teist protsessi ei tee laske selle muutuja uuendada. See on tõesti kloon, koopia, protsessid ei jaga peaaegu midagi. See on tõesti kasulik: saate ette valmistada palju andmeid ja seejärel hargneda () ning kasutada neid andmeid kõigis kloonides.

Eraldamine algab siis, kui fork () tagastab väärtuse. Algne protsess (seda nimetatakse vanemprotsess) saab kloonitud protsessi ID. Teisel pool kloonitud protsess (seda nimetatakse lapse protsess) saab 0 numbri. Nüüd peaksite mõistma, miks ma olen laused kahvli () järele asetanud if / else if-lausetega. Tagasiväärtuse abil saate õpetada last tegema midagi muud, kui vanem teeb - ja uskuge mind, see on kasulik.

Ühelt poolt teeb laps ülaltoodud näidiskoodis ülesande, mis võtab aega 5 sekundit ja prindib sõnumi. Kaua aega võtva protsessi jäljendamiseks kasutan unefunktsiooni. Seejärel väljub laps edukalt.

Teiselt poolt prindib vanem sõnumi, ootab, kuni laps lahkub, ja prindib lõpuks teise sõnumi. See, kui vanem oma last ootab, on oluline. Nagu näiteks, ootab vanem suurema osa sellest ajast oma lapse ootamiseks. Kuid ma oleksin võinud vanemale anda korralduse teha mistahes pikaajalisi ülesandeid, enne kui käskisin tal oodata. Nii oleks see ootamise asemel teinud kasulikke ülesandeid - lõppude lõpuks me sellepärast kasutamegi kahvel (), nr?

Kuid nagu ma eespool ütlesin, on see tõesti oluline vanem ootab oma lapsi. Ja see on oluline sellepärast zombie protsessid.

Kuidas ootamine on oluline

Vanemad soovivad üldjuhul teada, kas lapsed on töötlemise lõpetanud. Näiteks soovite käivitada ülesandeid paralleelselt, kuid sa kindlasti ei taha vanem lahkuda enne, kui laps on valmis, sest kui see juhtuks, annaks shell viivituse, kui lapsed pole veel lõpetanud - mis on imelik.

Ootefunktsioon võimaldab oodata, kuni üks lapseprotsessidest lõpetatakse. Kui vanem helistab kahekordselt (), peab ta ka kümme korda ootama (), üks kord igale lapsele loodud.

Aga mis juhtub, kui vanemad kutsuvad ootefunktsiooni, kui kõigil lastel on juba väljunud? Seal on vaja zombiprotsesse.

Kui laps väljub enne, kui vanema kõned ootavad (), laseb Linuxi tuum lapsel väljuda aga see hoiab piletit alles lapse ütlemine on väljunud. Siis, kui vanem helistab ootama (), leiab ta pileti, kustutab selle pileti ja funktsioon oodata () naaseb kohe sest see teab, et vanem peab teadma, kui laps on lõpetanud. Seda piletit nimetatakse a zombie protsess.

Sellepärast on oluline, et vanema kõned ootaksid (): kui see seda ei tee, jäävad zombiprotsessid mällu ja Linuxi kernelisse ei saa hoia palju zombiprotsesse mälus. Kui limiit on täis, on teie arvuti iei saa uut protsessi luua ja nii jääte a väga halb kuju: ühtlane protsessi tapmiseks peate võib-olla selleks looma uue protsessi. Näiteks kui soovite protsessi lõpetamiseks avada oma tegumihalduri, ei saa te seda teha, sest teie tegumihaldur vajab uut protsessi. Isegi kõige hullem, sa ei saa tappa zombie protsess.

Seetõttu on ootel helistamine oluline: see võimaldab kernelit korista ära lapse protsess, selle asemel, et jätkata lõpetatud protsesside loendit. Ja mis siis, kui vanem lahkub ilma kunagi helistamata oota ()?

Õnneks ei saa keegi teine, kui vanem lõpetatakse, nende laste jaoks oodata () ilma põhjuseta hoida neid zombiprotsesse. Seega, kui vanem lahkub, kõik alles zombie protsessid selle vanemaga seotud eemaldatakse. Zombie protsessid on tõesti kasulik ainult selleks, et võimaldada vanemaprotsessidel leida, et laps lõpetas enne, kui vanem kutsus ootama.

Nüüd võiksite eelistada mõningaid turvameetmeid, mis võimaldavad teil kahvlit kõige paremini kasutada ilma probleemideta.

Lihtsad reeglid kahvli tööks ettenähtud viisil

Esiteks, kui teate mitmikeermelist lugemist, siis ärge palun hargitage programmi niitide abil. Tegelikult vältige mitme samaaegse kasutamise tehnoloogia segamist. fork eeldab töötamist tavalistes C-programmides, kavatseb kloonida ainult ühe paralleelülesande, mitte rohkem.

Teiseks vältige enne kahvlit () failide avamist või avamist. Failid on üks ainus asi jagatud ja mitte kloonitud vanema ja lapse vahel. Kui loete vanemana 16 baiti, liigutab see lugemiskursorit 16 baidist edasi mõlemad vanemas ja lapsel. Halvim, kui laps ja vanem kirjutavad baiti sama fail samal ajal võivad vanema baidid olla segatud lapse baitidega!

Selguse huvides ei soovi te tõesti väljaspool STDINi, STDOUTI ja STDERRi ühtegi avatud faili kloonidega jagada.

Kolmandaks, olge pistikupesade suhtes ettevaatlik. Pistikupesad on ka jagatud vanema ja lapse vahel. See on kasulik pordi kuulamiseks ja siis lasta mitmel lapstöötajal olla valmis uue kliendiühendusega hakkama saama. Kuid, kui seda valesti kasutate, satute hätta.

Neljandaks, kui soovite helistada konksil fork (), tehke seda äärmiselt ettevaatlik. Võtame selle koodi:

/ * ÄRGE KOOSTAGE SEDA * /
const int targetFork = 4;
pid_t forkResult
 
jaoks (int i = 0; i < targetFork; i++)
forkResult = kahvel ();
/ *… * /
 

Kui loete koodi, võite eeldada, et see loob 4 last. Kuid see pigem loob 16 last. Selle põhjuseks on laps ka käivitab tsükli ja nii kutsuvad childid omakorda kahvli (). Kui silmus on lõpmatu, nimetatakse seda a kahvlipomm ja see on üks viis Linuxi süsteemi pidurdamiseks nii palju, et see enam ei tööta ja vajab taaskäivitamist. Lühidalt öeldes pidage meeles, et kloonisõjad pole ohtlikud ainult Tähesõdades!

Nüüd olete näinud, kuidas lihtne silmus võib valesti minna, kuidas kasutada silmusid kahvliga ()? Kui vajate tsüklit, kontrollige alati kahvli tagastusväärtust:

const int targetFork = 4;
pid_t forkResult;
int i = 0;
tee
forkResult = kahvel ();
/ *… * /
i ++;
while ((forkResult != 0 && forkResult != -1) && (i < targetFork));

Järeldus

Nüüd on aeg teil kahvliga () ise katsetada! Proovige aja optimeerimiseks uudseid viise, tehes toiminguid mitmes protsessori tuumas või tehke tausttöötlust, kui ootate faili lugemist!

Ärge kartke manuaalilehti man-käsu kaudu lugeda. Saate teada, kuidas fork () täpselt töötab, milliseid vigu võite saada jne. Ja naudi samaaegsust!

Lahing Wesnothi õpetuse eest
Battle for Wesnoth on üks populaarsemaid avatud lähtekoodiga strateegiamänge, mida saate praegu mängida. See mäng pole mitte ainult olnud väga pikka a...
0 A.D. Õpetus
Paljudest strateegiamängudest on 0 A.D. suudab silma paista põhjaliku tiitli ja väga sügava taktikalise mänguna, hoolimata sellest, et see on avatud l...
Unity3D õpetus
Sissejuhatus Unity 3D-sse Unity 3D on võimas mängude arendamise mootor. See on platvormidevaheline, mis võimaldab teil luua mänge mobiilseadmetele, ve...