17. olvasás: konkurencia
#### szoftver 6.005
biztonságos a hibáktól | könnyen érthető | készen áll a változásra |
---|---|---|
helyes ma és helyes az ismeretlen jövőben. | egyértelmű kommunikáció a jövőbeli programozókkal, beleértve a jövőbeli Önöket is. | úgy tervezték, hogy az átírások nélkül változzon. |
#### célok + üzenet átadása & megosztott memória + folyamatok & szálak + idő szeletelés + versenyfeltételek# # konkurencia * konkurencia * azt jelenti, hogy több számítás történik egyszerre. A konkurencia mindenütt jelen van a modern programozásban, akár tetszik, akár nem:+ több számítógép egy hálózatban+ több alkalmazás fut egy számítógépen+ több processzor egy számítógépen (ma gyakran több processzormag egyetlen chipen) valójában a konkurencia elengedhetetlen a modern programozásban:+ a webhelyeknek több egyidejű felhasználót kell kezelniük.+ A mobilalkalmazásoknak feldolgozásuk egy részét szervereken kell elvégezniük (“a felhőben”).+ A grafikus felhasználói felületek szinte mindig olyan háttérmunkát igényelnek, amely nem szakítja meg a felhasználót. Például az Eclipse lefordítja a Java kódot, miközben még mindig szerkeszti.A jövőben továbbra is fontos lesz a párhuzamos programozás. A processzor órajele már nem növekszik. Ehelyett egyre több magot kapunk a chipek minden új generációjával. Tehát a jövőben, annak érdekében, hogy egy számítás gyorsabban fusson, fel kell osztanunk egy számítást egyidejű darabokra.## Két modell az egyidejű Programozáshozkét közös modell létezik az egyidejű programozáshoz:* megosztott memória *és*üzenetátadás*.
**megosztott memória.** A párhuzamos memória modelljében a párhuzamos modulok kölcsönhatásba lépnek a memóriában Megosztott objektumok olvasásával és írásával. További példák a megosztott memória modellre: + A és B lehet két processzor (vagy processzormag) ugyanazon a számítógépen, ugyanazzal a fizikai memóriával.+ A és B lehet két program fut ugyanazon a számítógépen, megosztva egy közös fájlrendszer fájlokat tudnak olvasni és írni.+ A és B lehet két szál ugyanabban a Java programban (az alábbiakban elmagyarázzuk, mi a szál), ugyanazokat a Java objektumokat megosztva.
**üzenet átadása.** Az üzenetátadó modellben az egyidejű modulok kölcsönhatásba lépnek azáltal, hogy üzeneteket küldenek egymásnak egy kommunikációs csatornán keresztül. A modulok üzeneteket küldenek, az egyes modulokhoz érkező üzenetek pedig sorban állnak a kezeléshez. Például:+ a és B lehet két számítógép a hálózatban, amelyek hálózati kapcsolatokon keresztül kommunikálnak.+ A és B lehet webböngésző és webkiszolgáló-A kapcsolatot nyit meg B-vel, kér egy weboldalt, és B visszaküldi a weboldal adatait A-nak.+ A és B lehet azonnali üzenetküldő kliens és szerver.+ A és B lehet két program, amelyek ugyanazon a számítógépen futnak, és amelyek bemenetét és kimenetét egy cső köti össze, például az `LS | grep` parancssorba begépelve.## Processes, Threads, Time-slicing the message-passing and shared-memory modellek arról szólnak, hogy a párhuzamos modulok hogyan kommunikálnak. A párhuzamos modulok két különböző típusból állnak: folyamatokból és szálakból.** Folyamat**. A folyamat egy futó program példánya, amely * elszigetelt * ugyanazon a gépen lévő más folyamatoktól. Különösen a gép memóriájának saját privát része van.A folyamat absztrakció egy * virtuális számítógép*. Ettől a program úgy érzi, mintha az egész gép a magáé lenne-mintha egy friss számítógépet hoztak volna létre, friss memóriával, csak azért, hogy futtassa azt a programot.Csakúgy, mint a hálózaton keresztül csatlakoztatott számítógépek, a folyamatok általában nem osztanak meg memóriát közöttük. Egy folyamat egyáltalán nem fér hozzá egy másik folyamat memóriájához vagy objektumaihoz. A memória megosztása a folyamatok között *lehetséges* a legtöbb operációs rendszeren, de különleges erőfeszítéseket igényel. Ezzel szemben egy új folyamat automatikusan készen áll az üzenetek átadására, mert szabványos bemeneti & kimeneti folyamokkal jön létre, amelyek a `rendszer.ki ‘és `System.in’ a Java-ban használt patakok.** Szál**. A szál egy vezérlőhely egy futó programon belül. Gondolj rá úgy, mint egy helyre a futó programban, plusz a módszerhívások halmazára, amelyek arra a helyre vezettek, ahová vissza kell térni.Ahogy egy folyamat egy virtuális számítógépet képvisel, a szál absztrakció egy *virtuális processzort*jelent. Új szál készítése szimulálja egy friss processzor elkészítését a folyamat által képviselt virtuális számítógépen belül. Ez az új virtuális processzor ugyanazt a programot futtatja, és ugyanazt a memóriát osztja meg, mint a többi folyamatban lévő szál.A szálak automatikusan készen állnak a megosztott memóriára, mivel a szálak megosztják a folyamat összes memóriáját. Különleges erőfeszítésekre van szükség ahhoz, hogy egyetlen szálhoz privát “szál-helyi” memóriát kapjon. Szükség van az üzenetátadás explicit beállítására is, a várólista adatstruktúrák létrehozásával és használatával. Majd beszélünk arról, hogyan kell csinálni, hogy a jövőben olvasás.
hogyan lehet sok egyidejű szál csak egy vagy két processzorral a számítógépemben? Ha több szál van, mint processzor, akkor a párhuzamosságot **időszeletelés** szimulálja, ami azt jelenti, hogy a processzor vált a szálak között. A jobb oldali ábra azt mutatja, hogyan lehet három T1, T2 és T3 szálat időben szeletelni egy olyan gépen, amely csak két tényleges processzorral rendelkezik. Az ábrán az idő lefelé halad, így először az egyik processzor a T1 szálat, a másik a T2 szálat futtatja, majd a második processzor a T3 szál futtatására vált. Menet T2 egyszerűen szünetel, amíg a következő alkalommal szelet ugyanazon a processzoron vagy egy másik processzoron.A legtöbb rendszeren az időszeletelés kiszámíthatatlanul és nem meghatározóan történik, ami azt jelenti, hogy egy szál bármikor szüneteltethető vagy folytatható.
mitx:c613ec53e92840a4a506f3062c994673 folyamatok & Threads # # Shared Memory ExampleLet ‘ s nézd meg egy példát a megosztott memória rendszer. Ennek a példának az a lényege, hogy megmutassa, hogy az egyidejű programozás nehéz, mert finom hibái lehetnek.
képzeljük el, hogy egy banknak vannak olyan pénztárgépei, amelyek megosztott memóriamodellt használnak, így az összes pénztárgép ugyanazokat a fiókobjektumokat tudja olvasni és írni a memóriában.Annak szemléltetésére, hogy mi romolhat el, egyszerűsítsük le a bankot egyetlen számlára, egy dollár egyenleggel az `egyenleg`változóban tárolva, és két`befizetés ” és “visszavonás” művelettel, amelyek egyszerűen hozzáadnak vagy eltávolítanak egy dollárt: “`java// tegyük fel, hogy az összes pénzautomata egyetlen bankszámlán osztozik. privát statikus int egyenleg = 0;privát statikus érvénytelen betét() { balance = balance + 1;}privát statikus érvénytelen visszavonás() { balance = balance – 1;} “` Az ügyfelek a pénzautomatákat használják ilyen tranzakciók végrehajtására: “`javadeposit(); // tegyen egy dollárt inwithdraw(); // take it back out` ” ebben az egyszerű példában minden tranzakció csak egy dolláros befizetés, amelyet egy dolláros kifizetés követ, tehát a számlán lévő egyenleget változatlanul kell hagynia. A nap folyamán a hálózatunk minden pénztárgépe egy sor betéti / visszavonási tranzakciót dolgoz fel.”‘java / / minden ATM egy csomó tranzakciót hajt végre, amelyek / / módosítják az egyenleget, de változatlanul hagyják afterwardprivate static void cashMachine () { for (int i = 0; i < TRANSACTIONS_PER_MACHINE; ++i) { betét (); / / tegyen egy dollárt a visszavonásba(); // take it back out }} ” így a nap végén, függetlenül attól, hogy hány pénztárgép működött, vagy hány tranzakciót dolgoztunk fel, számítanunk kell arra, hogy a számlaegyenleg továbbra is 0 lesz.De ha futtatjuk ezt a kódot, gyakran felfedezzük, hogy a nap végén az egyenleg *nem* 0. Ha egynél több` cashMachine () `hívás fut egyszerre-mondjuk ugyanazon számítógép különböző processzorain -, akkor az` egyenleg ‘ nem lehet nulla a nap végén. Miért ne?## InterleavingHere egy dolog, ami megtörténhet. Tegyük fel, hogy két Bankautomata, A és B egyszerre dolgozik egy betéten. A deposit () lépés általában alacsony szintű processzor utasításokra bomlik: “‘get balance (balance=0) add 1 Írja vissza az eredményt (balance=1)`’ amikor A és B egyidejűleg fut, ezek az alacsony szintű utasítások interleave egymással (néhány lehet, hogy bizonyos értelemben egyidejű is, de egyelőre csak aggódjunk az interleaving miatt):”‘A get balance (balance=0) a add 1 A írja vissza az eredményt (balance=1) B get balance (balance=1) B add 1 B írja vissza az eredményt (balance=2)“Ez az átlapolás rendben van-mi a végén egyensúly 2, így mind A és B sikeresen tesz egy dollár. De mi lenne, ha az átlapolás így nézne ki` “‘ a get balance (balance=0)B get balance (balance=0) a add 1 B add 1 A írja vissza az eredményt (balance=1) B írja vissza az eredményt (balance=1)“az egyenleg most 1 — A Dollár Elveszett! A és B egyszerre olvasták ki az egyenleget, külön kiszámították a végső egyenleget, majd rohantak, hogy visszatárolják az új egyenleget-ami nem vette figyelembe a másik befizetését.## Versenyfeltételez egy példa a * * versenyfeltételre**. A versenyfeltétel azt jelenti, hogy a Program helyessége (az utófeltételek és invariánsok kielégítése) az események relatív időzítésétől függ az A és B párhuzamos számításokban.”Az események egyes interleavingjei rendben lehetnek abban az értelemben, hogy összhangban vannak azzal, amit egyetlen, nem aktuális folyamat eredményezne, de más interleavings hibás válaszokat eredményez-megsérti az utófeltételeket vagy az invariánsokat.## A kód módosítása nem segíta bankszámla kód ezen verzióinak mindegyike ugyanazt a versenyfeltételt mutatja: “‘java / / 1private statikus érvénytelen befizetés () { balance = balance + 1;} privát statikus érvénytelen visszavonás () { balance = balance-1;} “”” java / / 2private statikus érvénytelen befizetés () { balance + = 1;} privát statikus érvénytelen visszavonás () { balance – = 1;} “””java / / version 3private static void deposit() { ++balance;}private static void visszavonása() { –balance;}“nem lehet megmondani, csak nézi Java kódot, hogy a processzor fog végrehajtani. Nem lehet megmondani, mi lesz az oszthatatlan művelet — az atomi művelet–. Ez nem atomic csak azért, mert ez egy sor Java. Csak egyszer érinti az egyensúlyt, csak azért, mert az egyenleg-azonosító csak egyszer fordul elő a sorban. A Java fordító, sőt maga a processzor sem vállal kötelezettséget arra vonatkozóan, hogy milyen alacsony szintű műveleteket generál a kódjából. Valójában egy tipikus modern Java fordító pontosan ugyanazt a kódot állítja elő mindhárom verzióhoz!A legfontosabb tanulság az, hogy egy kifejezés megnézésével nem lehet megmondani, hogy biztonságos lesz-e a verseny körülményeitől.
## az Átrendezés még ennél is rosszabb. A bankszámlaegyenleg versenyfeltétele a különböző processzorok egymást követő műveleteinek különböző interleavingjeivel magyarázható. De valójában, ha több változót és több processzort használ, akkor nem is számíthat arra, hogy ezek a változók ugyanabban a sorrendben jelennek meg.Íme egy példa` “‘ javaprivate logikai kész = hamis; private Int answer = 0;/ / computeAnswer fut egy threadprivate void computeAnswer () { answer = 42; ready = true;} / / useAnswer fut egy másik threadprivate void useAnswer () {while (!kész) {szál.hozam ();} ha (válasz = = 0) dobjon új RuntimeException-t(“a válasz nem volt kész!”);} “‘Két módszerünk van, amelyek különböző szálakban futnak. a ‘computeAnswer’ hosszú számítást végez, végül elkészíti a 42-es választ, amelyet a válaszváltozóba helyez. Ezután a` ready ‘ változót true értékre állítja, hogy jelezze a másik szálban futó metódusnak, a `useAnswer` – nek, hogy a válasz készen áll a használatára. A kódot nézve a ‘válasz `a kész beállítása előtt van beállítva, tehát ha a` useAnswer `a` kész ` – et igaznak látja, akkor ésszerűnek tűnik feltételezni, hogy a` válasz’ 42 lesz, igaz? Nem így van.A probléma az, hogy a modern fordítók és processzorok sok mindent megtesznek annak érdekében, hogy a kód gyors legyen. Az egyik ilyen dolog a változók ideiglenes másolatainak készítése, mint például az answer and ready gyorsabb tárolásban (regiszterek vagy gyorsítótárak egy processzoron), és ideiglenesen dolgozni velük, mielőtt végül visszahelyeznék őket a hivatalos helyre a memóriában. A storeback más sorrendben fordulhat elő, mint a kódban manipulált változók. Íme, mi történhet a borítók alatt (de Java szintaxisban kifejezve, hogy világossá váljon). A processzor hatékonyan létrehoz két ideiglenes változót ` ‘tmpr’ és ‘tmpa’, hogy manipulálja a kész mezőket és válaszoljon:”‘javaprivate void computeAnswer () {logikai tmpr = kész; int tmpa = válasz; tmpa = 42; tmpr = true; ready = tmpr; // < – mi történik, ha a useAnswer () ide kapcsol? // kész van beállítva, de a válasz nem. answer = tmpa;}“mITX:2bf4beb7ffd5437bbbb9c782bb99b54e versenyfeltételek## Üzenetátadási példa
most nézzük meg a bankszámla példánk üzenetátadási megközelítését.Most már nem csak a pénztárgép modulok, de a számlák modulok is. A modulok egymás közötti üzenetek küldésével lépnek kölcsönhatásba. A beérkező kérések sorba kerülnek, hogy egyenként kezeljék őket. A feladó nem hagyja abba a munkát, miközben várja a választ a kérésére. Több kérést kezel a saját sorából. A kérésre adott válasz végül újabb üzenetként tér vissza.Sajnos az üzenet átadása nem zárja ki a versenyfeltételek lehetőségét. Tegyük fel, hogy minden fiók támogatja a ‘Get-balance` és a ‘visszavonás’ műveleteket, a megfelelő üzenetekkel. Két felhasználó, a cash machine a és B, mindketten próbálnak felvenni egy dollárt ugyanarról a számláról. Először ellenőrzik az egyenleget, hogy megbizonyosodjanak arról, hogy soha nem vesznek fel többet, mint amennyit a számla tart, mert a folyószámlahitelek nagy banki büntetéseket váltanak ki:“get-balanceif egyenleg >= 1, majd visszavonja az 1-et“a probléma ismét az átlapolás, de ezúttal a bankszámlára küldött *üzenetek* átlapolása, nem pedig az A és B által végrehajtott *utasítások*. Ha a számla egy dollárral kezdődik benne, akkor az üzenetek átlapolása megtéveszti A-t és B-t abban a hitben, hogy mindketten felvehetnek egy dollárt, ezáltal túllépve a számlát?Az egyik lecke itt az, hogy gondosan meg kell választania az üzenetátadó modell műveleteit. a ‘pénzkivonás, ha elegendő’ jobb művelet lenne, mint a `pénzkivonás`.## A konkurenciát nehéz tesztelni és Hibakeresniha nem győztünk meg arról, hogy a konkurencia trükkös, itt van a legrosszabb. Nagyon nehéz felfedezni a versenyfeltételeket teszteléssel. És még akkor is, ha egy teszt hibát talált, nagyon nehéz lehet lokalizálni a program azon részére, amely azt okozza.A párhuzamossági hibák nagyon rossz reprodukálhatóságot mutatnak. Nehéz elérni, hogy kétszer ugyanúgy történjenek. Az utasítások vagy üzenetek átlapolása a környezet által erősen befolyásolt események relatív időzítésétől függ. A késéseket más futó programok, más hálózati forgalom, az operációs rendszer ütemezési döntései, a processzor órajelének változása stb. Minden alkalommal, amikor fut egy programot tartalmazó verseny feltétele, akkor kap más viselkedést. Az ilyen típusú bogarak * * heisenbugs**, amelyek nem determinisztikusak és nehéz reprodukálni, szemben a “bohrbug” – val, amely ismételten megjelenik, amikor megnézzük. A szekvenciális programozás szinte minden hibája bohrbugs.A heisenbug még akkor is eltűnik, ha megpróbálja megnézni a `println` vagy a `debugger`segítségével! Ennek oka az, hogy a nyomtatás és a hibakeresés sokkal lassabb, mint más műveletek, gyakran 100-1000x lassabb, hogy drasztikusan megváltoztatják a műveletek időzítését és az átlapolást. Tehát egy egyszerű nyomtatási utasítás beillesztése a cashMachine () – be:“javaprivate statikus void cashMachine() { for (int i = 0; i < TRANSACTIONS_PER_MACHINE; ++i) { betét(); // tegyen egy dollárt a visszavonásba(); // vegye vissza a rendszert.kifelé.println (balance); / / teszi a hiba eltűnik! }}“`…és hirtelen az egyenleg mindig 0, ahogy kívánatos, és a hiba úgy tűnik, hogy eltűnik. De csak maszkos, nem igazán rögzített. A változás az időzítés valahol máshol a programban hirtelen, hogy a hiba jön vissza.A párhuzamosságot nehéz helyrehozni. Ennek az olvasásnak az a lényege, hogy kissé megijesztsen. A következő néhány olvasatban elvi módszereket fogunk látni az egyidejű programok tervezésére, hogy azok biztonságosabbak legyenek az ilyen típusú hibáktól.mitx: 704b9c4db3c6487c9f1549956af8bfc8 tesztelési konkurencia # # összefoglaló + konkurencia: egyszerre futó több számítás + megosztott memória & üzenetátadó paradigmák + folyamatok & szálak + a folyamat olyan, mint egy virtuális számítógép; a szál olyan, mint egy virtuális processzor+ versenyfeltételek + amikor az eredmény helyessége (utófeltételek és invariánsok) az események relatív időzítésétől függezek az ötletek a jó szoftver három fő tulajdonságához kapcsolódnak, többnyire rossz módon. A párhuzamosság szükséges, de komoly problémákat okoz a helyesség szempontjából. A következő néhány olvasatban megpróbáljuk kijavítani ezeket a problémákat.+ * * Biztonságos a hibáktól.** A konkurencia hibák a legnehezebb hibák, amelyeket meg kell találni és kijavítani, és gondos tervezést igényelnek, hogy elkerüljék.+ * * Könnyen érthető.** Megjósolni, hogy egyidejű kódot lehet interleave más egyidejű kódot nagyon nehéz a programozók csinálni. A legjobb, ha úgy tervezünk, hogy a programozóknak ne kelljen erre gondolniuk. + * * Készen áll a változásra.** Itt nem különösebben releváns.