Kuulsate Dinning-filosoofide probleemi illustratsioon

Paralleelsus vs sündmussilmus vs sündmussilmus + samaaegsus

Kõigepealt selgitame terminoloogiat.
Paralleelsus - tähendab, et teil on mitu protsessijärjekorda mitmel protsessori tuumal / lõimel. Kuid see erineb täielikult paralleelsest täitmisest, paralleelne täitmine ei sisalda paralleeljuhtumi jaoks mitut ülesandejärjekorda. Täieliku paralleelse täitmise jaoks on meil vaja 1 CPU südamikku / lõime, mida enamikul juhtudel me ei saa määratleda. Sellepärast tähendab kaasaegse tarkvaraarenduse jaoks paralleelne programmeerimine mõnikord “samaaegsust”, ma tean, et see on kummaline, kuid ilmselgelt see on see, mis meil praegu on (see sõltub OS-i protsessori / keermemudelist).
Event Loop - tähendab ühe keermega lõpmatut tsüklit, mis teeb ühe ülesande korraga ja see ei moodusta mitte ainult ühte ülesandejärjekorda, vaid eelistab ka ülesandeid, kuna sündmusahelaga on teil täitmiseks ainult üks ressurss (1 lõim), seega täitmiseks mõned ülesanded vajavad kohe tähtsuse järjekorda seadmist. Mõne sõnaga nimetatakse seda programmeerimislähenemist keerukaks programmeerimiseks, kuna korraga saab täita ainult ühte ülesannet / funktsiooni / toimingut ja kui midagi muudate, siis muutuks see juba järgmise ülesande täitmise ajal.

Samaaegne programmeerimine

Kaasaegsetes arvutites / serverites on meil vähemalt 2 protsessori tuuma ja min. 4 protsessori niiti. Kuid serverites on nüüd keskm. serveril on vähemalt 16 protsessori lõime. Nii et kui kirjutate tarkvara, mis vajab teatavat jõudlust, peaksite kindlasti kaaluma selle loomist nii, et see kasutaks kõiki serveris saadaolevaid protsessori tuuma.

See pilt kujutab samaaegsuse põhimudelit, kuid kokkuvõtlikult pole see kuvamine nii lihtne :)

Paralleelsuse programmeerimine on mõne jagatud ressursi korral muutumas tõeliselt keeruliseks, näiteks saame vaadata seda lihtsat samaaegset koodi.

// Vale samaaegus Go-keelega
paketi peamine
import (
   "fmt"
   "aeg"
)
var SharedMap = make (kaardi [string] string)
func changeMap (väärtus string) {
    SharedMap ["test"] = väärtus
}
func main () {
    mine muutma kaarti ("väärtus1")
    mine muutma kaarti ("väärtus2")
    aeg.Uneaeg (aeg.Millisekund * 500)
    fmt.Println (SharedMap ["test"])
}
// Sellega trükitakse väärtus "väärtus1" või "väärtus2", mida me täpselt ei tea!

Sel juhul vallandab Go 2 samaaegset tööd tõenäoliselt erinevatesse protsessorituumadesse ja me ei saa ennustada, milline neist kõigepealt täidetakse, nii et me ei teaks, mida lõpus kuvatakse.
Miks? - See on lihtne! Kavandame erinevatele protsessorituumadele 2 erinevat ülesannet, kuid nad kasutavad ühte jagatud muutujat / mälu, nii et nad mõlemad muudavad seda mälu ja mõnel juhul ka programmi krahhi / erandi korral.

Nii et samaaegse programmeerimise täitmise ennustamiseks peame kasutama mõnda lukustusfunktsiooni, näiteks Mutex. Selle abil saame selle jagatud mäluressursi lukustada ja muuta see korraga kättesaadavaks ainult ühe ülesande jaoks.
Seda programmeerimisstiili nimetati Blokeerimine, kuna tegelikult blokeerime kõik ülesanded seni, kuni praegune ülesanne on jagatud mäluga tehtud.

Enamikule arendajatest ei meeldi samaaegne programmeerimine, kuna samaaegsus ei tähenda alati jõudlust. See sõltub konkreetsetest juhtumitest.

Ühe keermega üritussilm

See tarkvaraarenduse lähenemisviis on palju lihtsam kui samaaegne programmeerimine. Sest põhimõte on väga lihtne. Teil on korraga ainult üks ülesande täitmine. Ja sel juhul pole teil ühiste muutujate / mäluga probleeme, kuna programm on paremini etteaimatav ühe ülesande korral korraga.

Järgneb üldine voog
1. Sündmuse emitter lisab järgmise sündmusetsükli jaoks ülesande sündmuste järjekorda
2. Event Loop ülesande saamine sündmuste järjekorrast ja selle töötlemine käitlejate põhjal

Võimaldab kirjutada sama näite sõlmega.js

las SharedMap = {};
const changeMap = (väärtus) => {
    return () => {
        SharedMap ["test"] = väärtus
    }
}
// 0 Aegumine tähendab, et teeme järgmise tsükli jaoks järjekorras uue ülesande
setTimeout (changeMap ("väärtus1"), 0);
setTimeout (changeMap ("väärtus2"), 0);
setTimeout (() => {
   console.log (SharedMap ["test"])
}, 500);
// sel juhul prindib Node.js väärtuse "value2", kuna see on üksik
// keermestatud ja sellel on "ainult üks ülesannete järjekord"

Nagu võite ette kujutada, on koodkood etteaimatavam kui samaaegse Go-näite korral ja seetõttu, et Node.js töötab ühes keermestatud režiimis, kasutades JavaScripti sündmuseahelat.

Mõnel juhul annab sündmuse ahel rohkem jõudlust kui samaaegsus, mitte blokeeriva käitumise tõttu. Väga heaks näiteks on võrgurakendused, kuna nad kasutavad ühe võrguühenduse ressurssi ja töötlevad andmeid ainult siis, kui see on Thread Safe Event Loops'i abil saadaval.

Paralleelsus + sündmuse ahel - keermebassein keerme ohutusega

Rakenduste muutmine ainult samaaegseteks võib olla väga keeruline, kuna mälu riknemise vigu leidub igal pool või lihtsalt hakkab teie rakendus iga toimingu toiminguid blokeerima. Eriti kui soovite saada maksimaalset jõudlust, peate mõlemad ühendama!

Heidame pilgu Nginxi veebiserveri struktuuri jaotusele Thread Pool + Event Loop

Põhivõrgustiku ja konfiguratsioonitöötluse teeb turvalisuse huvides Worker Event Loop ühes lõimes, kuid kui Nginx peab mõnda faili lugema või töötlema toiminguid blokeerivaid HTTP päringu päiseid / keha, saadab ta selle ülesande oma lõimebasseini. samaaegseks töötlemiseks. Ja kui ülesanne on tehtud, saadetakse tulemus sündmuse ahelasse, et niidit ohutult töödelda.

Nii et selle struktuuri kasutamisel saate nii niidi turvalisuse kui ka samaaegsuse, mis võimaldab jõudluse saavutamiseks kasutada kõiki protsessori südamikke ja hoida blokeerimata põhimõtet ühe keermestatud sündmuse ahelaga.

Järeldus

Suur osa tarkvarast on kirjutatud puhtalt samaaegselt või ühe keermega sündmuste ahelaga, kuid ühendades mõlemad ühe rakenduse sees, on täidesaatvate rakenduste kirjutamine ja kõigi saadaolevate protsessoriressursside kasutamine lihtsam.