Mynd af frægu „vandamáli heimspekingar heimspekinga“

Concurrency vs Event Loop vs Event Loop + Concurrency

Fyrst af öllu skulum við útskýra hugtök.
Samhliða - þýðir að þú ert með margar verkefnisraðir á mörgum gjörðum / þráðum örgjörva. En það er allt öðruvísi en samhliða framkvæmd, samhliða framkvæmd myndi ekki innihalda mörg verkefni biðröð fyrir samhliða tilfelli, við munum þurfa 1 CPU kjarna / þráð fyrir hvert verkefni fyrir fullkomna samhliða framkvæmd, sem í flestum tilvikum getum við ekki skilgreint. Það er ástæðan fyrir nútíma hugbúnaðarþróun Samhliða forritun þýðir stundum „Samtímis“, ég veit að það er skrýtið, en augljóslega er það það sem við höfum í augnablikinu (það fer eftir OS cpu / þráðargerð).
Event Loop - þýðir einþráður óendanlegur hringrás sem er að gera eitt verkefni í einu og það er ekki aðeins að búa til eina verkefna biðröð, heldur er það einnig að forgangsraða verkefnum, því með atburðarlykkju hefurðu aðeins eitt úrræði til framkvæmdar (1 þráður) svo til að keyra sum verkefni strax þú þarft að forgangsraða verkefnum. Í sumum orðum er þessi forritunaraðferð kölluð Thread Safe Forritun vegna þess að aðeins eitt verkefni / aðgerð / aðgerð var hægt að framkvæma í einu, og ef þú ert að breyta einhverju væri henni þegar breytt við næstu verkefni.

Samhliða forritun

Í nútíma tölvum / netþjónum höfum við að minnsta kosti 2 CPU algerlega og mín. 4 CPU þræðir. En á netþjónum er nú meðaltal. netþjónninn hefur að minnsta kosti 16 CPU þræði. Svo ef þú ert að skrifa hugbúnað sem þarfnast nokkurra frammistöðu ættirðu örugglega að íhuga að gera hann á þann hátt að hann noti allar CPU kjarna sem eru í boði á netþjóninum.

Þessi mynd birtir grunnlíkön af samhliða en af ​​kölluðu er það ekki svo auðvelt að hún birtist :)

Samræmis forritun er að verða mjög erfitt með nokkrum samnýttum auðlindum, til dæmis er hægt að kíkja á þennan Go einfalda samhliða kóða.

// Röng samtímis við Go tungumál
aðal pakki
flytja inn (
   "fmt"
   "tími"
)
var SharedMap = gera (kort [strengur] strengur)
func ChangeMap (gildi strengur) {
    SharedMap ["test"] = gildi
}
func aðal () {
    farðu að breyta korti ("gildi1")
    farðu að breyta korti ("gildi2")
    time.Sleep (time.Millisecond * 500)
    fmt.Println (SharedMap ["test"])
}
// Þetta mun prenta „gildi1“ eða „gildi2“ sem við vitum ekki nákvæmlega!

Í þessu tilfelli mun Go hleypa af stokkunum 2 samtímis störfum líklega í mismunandi CPU algerlega og við getum ekki sagt fyrir um hver verður framkvæmd fyrst, svo við myndum ekki vita hvað verður birt í lokin.
Af hverju? - Það er einfalt! Við erum að skipuleggja 2 mismunandi verkefni í mismunandi CPU algerlega en þeir eru að nota staka sameiginlega breytu / minni, þannig að þau bæði breyta því minni og í sumum tilvikum væri um að ræða forritsbrask / undantekningu.

Svo til að spá fyrir um framkvæmd samtímis forritunar verðum við að nota nokkrar læsingaraðgerðir eins og Mutex. Með því getum við læst það sameiginlega minni auðlind og gert það aðgengilegt aðeins fyrir eitt verkefni í einu.
Þessi forritunarstíll kallast Blocking vegna þess að við lokum í raun öll verkefni þar til núverandi verkefni er gert með sameiginlegu minni.

Flestir verktaki líkar ekki samtímis forritun vegna þess að samtímis þýðir ekki alltaf árangur. Það fer eftir sérstökum tilvikum.

Single Threaded Event Loop

Þessi hugbúnaðarþróun er leið einfaldari en samtímis forritun. Vegna þess að meginreglan er mjög einföld. Þú hefur aðeins eina verk framkvæmd í einu. Og í þessu tilfelli áttu ekki í neinum vandræðum með sameiginlegar breytur / minni, vegna þess að forrit er fyrirsjáanlegra með einu verkefni í einu.

Almennt flæði er í kjölfarið
1. Event Emitter bætir verkefni við Event biðröð til að framkvæma í næstu hringrás
2. Atburðarlykkja að fá verkefni úr viðburðarröð og vinna úr því miðað við meðhöndlunarmenn

Leyfum að skrifa sama dæmi með node.js

láta SharedMap = {};
const changeMap = (gildi) => {
    skila () => {
        SharedMap ["test"] = gildi
    }
}
// 0 Timeout þýðir að við erum að gera nýja verkefni í biðröð fyrir næstu lotu
setTimeout (breytingakort ("gildi1"), 0);
setTimeout (breytingakort ("gildi2"), 0);
setTimeout (() => {
   console.log (SharedMap ["test"])
}, 500);
// í þessu tilfelli mun Node.js prenta „gildi2“ vegna þess að það er stakt
// snittari og það er með „aðeins eina verkefna biðröð“

Eins og þú getur ímyndað þér í þessu tilfelli er kóða fyrirsjáanlegri en með samhliða Go dæmi, og það er vegna þess að Node.js keyrir í einum snittari stillingu með því að nota JavaScript atburðarlykkju.

Í sumum tilvikum skilar atburðarás meiri árangri en samhliða vegna hegðunar sem ekki hindrar. Mjög gott dæmi um netforrit, vegna þess að þau nota eingöngu netsambandsauðlindir og vinna úr gögnum þegar þau eru fáanleg með Thread Safe Event Loops.

Samhliða + atburðarlykkja - þráðarlaug með þráðöryggi

Að búa til forrit aðeins samtímis gæti verið mjög krefjandi, vegna þess að galla í spillingarminnum væri alls staðar eða einfaldlega að forritið þitt byrji að hindra aðgerðir við hvert verkefni. Sérstaklega ef þú vilt ná hámarksárangri þarftu að sameina bæði!

Við skulum skoða Thread Pool + Event Loop líkan úr Nginx Web Server Structure

Aðalnetvinnsla og stillingarvinnsla er gerð af Worker Event Loop í einum þræði til öryggis, en þegar Nginx þarf að lesa einhverja skrá eða þarf að vinna úr HTTP beiðni hausum / líkama, sem eru að hindra aðgerðir, er það að senda það verkefni í þráðarlaugina sína til samtímis vinnslu. Og þegar verki er lokið, er niðurstaðan send aftur í atburðarásina til að ná fram þrá öruggri vinnslu.

Svo þú notar þessa uppbyggingu og þú færð bæði þráðöryggi og samvirkni, sem gerir þér kleift að nota allar CPU-kjarnar fyrir frammistöðu og halda meginreglu ekki með einni snittari atburðarlykkju.

Niðurstaða

A einhver fjöldi af hugbúnaður er skrifaður með hreinu samhljómi eða með hreinni stakri snittari atburðarlykkju, en að sameina bæði inni í einu forriti og gera það auðveldara að skrifa afkastamikil forrit og nota öll tiltæk CPU-auðlindir.