Milyen célokra használnak többszálú rendszereket. Nyolc egyszerű szabály a többszálas alkalmazások fejlesztésére

Melyik téma okozza a legtöbb kérdést és nehézséget a kezdőknek? Amikor megkérdeztem Alexander Pryakhin tanáromat és Java programozót, ő azonnal azt válaszolta: „Multithreading”. Köszönöm neki az ötletet és a segítséget a cikk elkészítésében!

Megvizsgáljuk az alkalmazás belső világát és folyamatait, kitaláljuk, hogy mi a többszálúság lényege, mikor hasznos, és hogyan kell megvalósítani - példaként a Java -t. Ha más OOP nyelvet tanul, ne aggódjon: az alapelvek ugyanazok.

A patakokról és azok eredetéről

A többszálúság megértéséhez először is értsük meg, mi az a folyamat. A folyamat egy virtuális memória és erőforrás, amelyet az operációs rendszer kioszt egy program futtatásához. Ha ugyanazon alkalmazás több példányát nyitja meg, a rendszer mindegyikhez lefoglal egy folyamatot. A modern böngészőkben minden lapért külön folyamat lehet felelős.

Valószínűleg találkozott a Windows "Feladatkezelővel" (Linuxon ez a "Rendszerfigyelő"), és tudja, hogy a szükségtelen futó folyamatok betöltik a rendszert, és a legnehezebbek gyakran lefagynak, ezért erőszakkal kell leállítani őket .

De a felhasználók szeretik a többfeladatos munkát: ne etessék őket kenyérrel - hadd nyissanak ki egy tucat ablakot, és ugorjanak oda -vissza. Van egy dilemma: biztosítani kell az alkalmazások egyidejű működését, és ugyanakkor csökkenteni kell a rendszer terhelését, hogy ne lassuljon le. Tegyük fel, hogy a hardver nem tud lépést tartani a tulajdonosok igényeivel - a problémát szoftver szinten kell megoldani.

Azt akarjuk, hogy a processzor több utasítást hajtson végre, és több adatot dolgozzon fel időegységenként. Vagyis több végrehajtott kódot kell illesztenünk minden időszeletbe. Gondoljon a kódfuttatás egységére objektumként - ez egy szál.

Egy összetett eset könnyebben megközelíthető, ha több egyszerű esetre bontja. Így van ez a memóriával való munka során is: a "nehéz" folyamat szálakra oszlik, amelyek kevesebb erőforrást foglalnak el, és nagyobb valószínűséggel juttatják el a kódot a számológéphez (hogyan pontosan - lásd alább).

Minden alkalmazásnak legalább egy folyamata van, és minden folyamatnak van legalább egy szála, amelyet főszálnak neveznek, és amelyből szükség esetén újakat indítanak.

A szálak és a folyamatok közötti különbség

    A szálak a folyamathoz kiosztott memóriát használják, és a folyamatok saját memóriaterületet igényelnek. Ezért a szálak gyorsabban jönnek létre és fejeződnek be: a rendszernek nem kell minden alkalommal új címteret kiosztania nekik, majd elengednie.

    A folyamatok mindegyike saját adatokkal dolgozik - valamit csak a folyamatok közötti kommunikáció mechanizmusán keresztül cserélhetnek. A szálak közvetlenül hozzáférnek egymás adataihoz és erőforrásaihoz: amit megváltoztatott, az azonnal elérhető mindenki számára. A szál irányíthatja a "fickót" a folyamatban, míg a folyamat kizárólag a "lányait". Ezért a folyamok közötti váltás gyorsabb és a kommunikáció közöttük könnyebb.

Mi ebből a következtetés? Ha nagy mennyiségű adatot kell a lehető leggyorsabban feldolgoznia, ossza szét darabokra, amelyeket külön szálak feldolgozhatnak, majd ossza össze az eredményt. Ez jobb, mint az erőforrás-éhes folyamatok létrehozása.

De miért megy egy olyan népszerű alkalmazás, mint a Firefox, több folyamat létrehozására? Mivel a böngésző számára megbízható és rugalmas az elszigetelt lapok működése. Ha valami nem stimmel egy folyamattal, nem szükséges leállítani a teljes programot - lehetőség van az adatok legalább egy részének mentésére.

Mi az a többszálú

Tehát elérkeztünk a lényeghez. A többszálúság az, amikor az alkalmazás folyamata szálakra oszlik, amelyeket a processzor párhuzamosan dolgoz fel - egy időegység alatt.

A számítási terhelés két vagy több mag között oszlik meg, így az interfész és más programkomponensek nem lassítják egymás munkáját.

A többszálú alkalmazások egymagos processzorokon is futtathatók, de ezután a szálak sorrendben végrehajtásra kerülnek: az első működött, az állapota mentésre került - a másodikat hagyták működni, mentették - visszaállították az elsőhöz, vagy elindították a harmadikat, stb.

Az elfoglalt emberek panaszkodnak, hogy csak két kezük van. A folyamatoknak és programoknak annyi kezük lehet, amennyi a feladat lehető leggyorsabb elvégzéséhez szükséges.

Várjon egy jelre: szinkronizálás többszálas alkalmazásokban

Képzelje el, hogy több szál egyszerre próbálja megváltoztatni ugyanazt az adatterületet. Kinek a módosításait fogadják el végül, és kinek a módosításait? A zavartság elkerülése érdekében a megosztott erőforrások kezelésekor a szálaknak össze kell hangolniuk cselekedeteiket. Ehhez jelek segítségével cserélnek információt. Minden szál elmondja a többieknek, hogy mit csinál, és milyen változások várhatók. Tehát az összes szál adatai az erőforrások aktuális állapotáról szinkronizálva vannak.

Alapvető szinkronizáló eszközök

Kölcsönös kizárás (kölcsönös kizárás, rövidítve - mutex) - a "zászló" a szálhoz megy, amely jelenleg megosztott erőforrásokkal dolgozhat. Megszünteti más szálak hozzáférését a foglalt memóriaterülethez. Egy alkalmazásban több mutex is lehet, és megoszthatók a folyamatok között. Van egy fogás: a mutex arra kényszeríti az alkalmazást, hogy minden alkalommal hozzáférjen az operációs rendszer kerneljéhez, ami költséges.

Szemafor - lehetővé teszi, hogy korlátozza azon szálak számát, amelyek egy adott pillanatban hozzáférhetnek az erőforráshoz. Ez csökkenti a processzor terhelését, amikor szűk keresztmetszetek esetén hajt végre kódot. A probléma az, hogy az optimális szálak száma a felhasználó gépétől függ.

Esemény - olyan feltételt határoz meg, amelynél a vezérlés átkerül a kívánt szálra. A folyamok eseményadatokat cserélnek egymás tevékenységeinek fejlesztése és logikus folytatása érdekében. Az egyik megkapta az adatokat, a másik ellenőrizte helyességét, a harmadik a merevlemezre mentette. Az események a törlés módjában különböznek. Ha több szálat kell értesítenie egy eseményről, akkor manuálisan kell beállítania a törlés funkciót a jel leállítására. Ha csak egy célszál van, létrehozhat egy automatikus visszaállítási eseményt. Magát a jelet leállítja, miután elérte a folyamot. Az események sorba állíthatók a rugalmas áramlásszabályozás érdekében.

Kritikus rész - bonyolultabb mechanizmus, amely hurokszámlálót és szemaforot kombinál. A számláló lehetővé teszi a szemafor kezdésének elhalasztását a kívánt időre. Előnye, hogy a rendszermag csak akkor aktiválódik, ha a szakasz foglalt, és a szemaforot be kell kapcsolni. A többi időben a szál felhasználói módban fut. Sajnos egy szakasz csak egy folyamaton belül használható.

Hogyan lehet megvalósítani a többszálasítást Java -ban

A szálosztály felelős a Java szálak kezeléséért. Egy új szál létrehozása egy feladat végrehajtásához azt jelenti, hogy létre kell hozni a Thread osztály egy példányát, és hozzá kell rendelni a kívánt kódhoz. Ezt kétféleképpen lehet megtenni:

    alosztály Szál;

    implementálja a Runnable felületet az osztályában, majd adja át az osztálypéldányokat a Thread konstruktornak.

Bár nem fogjuk érinteni a patthelyzetek témáját, amikor a szálak blokkolják egymás munkáját és lefagynak, ezt a következő cikkre hagyjuk.

Java többszálas példa: ping-pong mutexekkel

Ha úgy gondolja, hogy valami szörnyűség fog történni, lélegezzen ki. A szinkronizációs objektumokkal való munkát szinte játékos módon fogjuk megfontolni: két szálat a mutex dob, de valójában egy valós alkalmazást fog látni, ahol egyszerre csak egy szál képes feldolgozni a nyilvánosan elérhető adatokat.

Először hozzunk létre egy osztályt, amely örökli a már ismert szál tulajdonságait, és írjunk egy kickBall metódust:

A PingPongThread nyilvános osztály kiterjeszti a szálat (PingPongThread (karakterlánc neve) (this.setName (név); // felülbírálja a szál nevét) @Orride public void run () (Ball ball = Ball.getBall (); while (ball.isInGame () ) (kickBall (labda);)) private void kickBall (Labdalabda) (if (! ball.getSide (). egyenlő (getName ())) (ball.kick (getName ());)))

Most vigyázzunk a labdára. Nálunk nem lesz egyszerű, de emlékezetes: hogy elmondhassa, ki ütötte meg, melyik oldalról és hányszor. Ehhez egy mutexet használunk: információkat fog gyűjteni az egyes szálak munkájáról - ez lehetővé teszi az elszigetelt szálak közötti kommunikációt. A 15. találat után kivesszük a labdát a játékból, nehogy súlyosan megsérüljünk.

Nyilvános osztályú labda (privát int rúgások = 0; privát statikus labdapéldány = új labda (); privát karakterlánc oldal = ""; privát labda () () statikus labda getBall () (visszatérési példány;) szinkronizált érvénytelen rúgás (karakterlánc lejátszási név) (rúgások ++; oldal = lejátszási név; System.out.println (rúgások + "" + oldal);) Karakterlánc getSide () (visszatérő oldal;) boolean isInGame () (return (rúgások< 15); } }

És most két játékos szál lép a színre. Nevezzük őket minden további nélkül Pingnek és Pongnak:

Nyilvános osztályú PingPongGame (PingPongThread player1 = új PingPongThread ("Ping"); PingPongThread player2 = új PingPongThread ("Pong"); Labdalabda; PingPongGame () (ball = Ball.getBall ();) érvénytelen startGame () dobja InterruptException ( .start (); player2.start ();))

"Tele emberek stadionja - ideje elkezdeni a mérkőzést." Hivatalosan bejelentjük a találkozó megnyitását - az alkalmazás fő osztályában:

Nyilvános osztályú PingPong (public static void main (String args) dobások InterruptException (PingPongGame game = new PingPongGame (); game.startGame ();))

Mint látható, itt nincs semmi dühös. Ez egyelőre csak bevezetés a többszálasításba, de már tudja, hogyan működik, és kísérletezhet - ne korlátozza a játék időtartamát, például az ütések számával, hanem például idővel. Később visszatérünk a többszálas témához - megnézzük a java.util.concurrent csomagot, az Akka könyvtárat és az illékony mechanizmust. Beszéljünk a multithreading Pythonban való megvalósításáról is.

A többszálas programozás alapvetően nem különbözik az eseményvezérelt grafikus felhasználói felületek írásától, vagy akár az egyszerű szekvenciális alkalmazások írásától. Itt érvényes minden fontos szabály, amely a beágyazást, az aggodalmak elkülönítését, a laza csatolást stb. Szabályozza. Sok fejlesztő azonban éppen azért nehezen ír többszálú programokat, mert figyelmen kívül hagyja ezeket a szabályokat. Ehelyett a szálakról és a szinkronizálási primitívekről szóló, sokkal kevésbé fontos ismereteket próbálják a gyakorlatba átültetni, amelyeket a kezdőknek szóló többszálas programozásról szóló szövegekből gyűjtöttek össze.

Tehát mik ezek a szabályok

Egy másik programozó, aki problémával szembesül, azt gondolja: "Ó, pontosan, szabályos kifejezéseket kell alkalmaznunk." És most már két problémája van - Jamie Zawinski.

Egy másik programozó, aki problémával szembesül, azt gondolja: "Ó, igen, itt streameket fogok használni." És most tíz problémája van - Bill Schindler.

Túl sok programozó, aki vállalja a többszálas kód írását, esik a csapdába, mint Goethe balladájának hőse. " A varázslótanonc". A programozó megtanulja, hogyan kell létrehozni egy csomó szálat, amelyek elvileg működnek, de előbb -utóbb kikerülnek az irányítás alól, és a programozó nem tudja, mit tegyen.

Ám a varázsló-lemorzsolódással ellentétben a szerencsétlen programozó nem reménykedhet egy erőteljes varázsló érkezésében, aki megpörgeti a pálcáját és helyreállítja a rendet. Ehelyett a programozó a legelvetemültebb trükkökre megy, és megpróbál megbirkózni a folyamatosan felmerülő problémákkal. Az eredmény mindig ugyanaz: túl bonyolult, korlátozott, törékeny és megbízhatatlan alkalmazást kapunk. Állandó fenyegetése van a patthelyzetnek és más veszélyeknek, amelyek a rossz, többszálas kódban rejlenek. Nem is beszélek megmagyarázhatatlan összeomlásokról, gyenge teljesítményről, hiányos vagy helytelen munkaeredményekről.

Talán elgondolkodtál: miért történik ez? Gyakori tévhit: "A többszálas programozás nagyon nehéz." De ez nem így van. Ha egy többszálas program megbízhatatlan, akkor általában ugyanazok miatt bukik meg, mint egy gyenge minőségű egyszálú program. Csak a programozó nem követi az alapozó, jól ismert és bevált fejlesztési módszereket. A többszálas programok csak összetettebbnek tűnnek, mert minél több párhuzamos szál megy rosszul, annál nagyobb rendetlenséget okoznak - és sokkal gyorsabban, mint egyetlen szál.

A "többszálas programozás összetettségéről" szóló tévhit széles körben elterjedt azoknak a fejlesztőknek köszönhetően, akik professzionálisan fejlesztették ki az egyszálú kód írását, először találkoztak többszálúsággal, és nem tudtak megbirkózni vele. De ahelyett, hogy újragondolnák előítéleteiket és munkaszokásaikat, makacsul rögzítik azt a tényt, hogy semmilyen módon nem akarnak dolgozni. Mentségeket keresve a megbízhatatlan szoftverekért és a határidők elmulasztásáért, ezek az emberek ugyanazt ismételgetik: "a többszálas programozás nagyon nehéz."

Kérjük, vegye figyelembe, hogy a fentiekben tipikus programokról beszélek, amelyek többszálúságot használnak. Valóban léteznek bonyolult többszálas forgatókönyvek-valamint bonyolult egyszálúak is. De nem gyakoriak. Általában a gyakorlatban semmi természetfeletti nem szükséges a programozótól. Az adatokat áthelyezzük, átalakítjuk, időnként számításokat végzünk, és végül az adatokat adatbázisba mentjük, vagy megjelenítjük a képernyőn.

Nincs semmi nehéz abban, hogy javítsuk az átlagos egyszálú programot, és többszálúvá alakítsuk. Legalábbis nem kellene. A nehézségek két okból adódnak:

  • a programozók nem tudják, hogyan kell alkalmazni egyszerű, jól bevált fejlesztési módszereket;
  • a többszálas programozásról szóló könyvekben közölt információk nagy része technikailag helyes, de teljesen alkalmatlan az alkalmazott problémák megoldására.

A legfontosabb programozási koncepciók univerzálisak. Egyszálú és többszálú programokra egyaránt alkalmazhatók. A patakok forgatagába fulladó programozók egyszerűen nem tanultak fontos tanulságokat, amikor elsajátították az egyszálú kódot. Ezt azért mondhatom, mert az ilyen fejlesztők ugyanazokat az alapvető hibákat követik el a többszálú és egyszálú programokban.

Talán a legfontosabb tanulság a programozástörténet hatvan évében: globális változékony állapot- gonosz... Igazi gonoszság. A globálisan megváltoztatható állapotra támaszkodó programok viszonylag nehezen érvelhetők, és általában megbízhatatlanok, mert túl sok mód van az állapot megváltoztatására. Nagyon sok tanulmány igazolta ezt az általános elvet, számtalan tervezési minta létezik, amelyek fő célja az adatok elrejtésének egyik vagy másik módja. Annak érdekében, hogy a programjait kiszámíthatóbbá tegyék, próbálja meg a lehető legtöbb módon megszüntetni a változékony állapotot.

Egyszálú soros programban az adatvesztés valószínűsége egyenesen arányos az adatokat módosítani képes összetevők számával.

Általános szabály, hogy nem lehet teljesen megszabadulni a globális államtól, de a fejlesztő nagyon hatékony eszközökkel rendelkezik az arzenáljában, amelyek lehetővé teszik, hogy szigorúan ellenőrizzék, mely programkomponensek képesek megváltoztatni az állapotot. Ezen kívül megtanultuk, hogyan lehet korlátozó API -rétegeket létrehozni a primitív adatszerkezetek körül. Ezért jól tudjuk ellenőrizni, hogy ezek az adatstruktúrák hogyan változnak.

A globálisan megváltoztatható állapot problémái fokozatosan nyilvánvalóvá váltak a 80-as évek végén és a 90-es évek elején, az eseményvezérelt programozás elterjedésével. A programok már nem "az elejétől" indultak, vagy egyetlen, előre látható végrehajtási utat követtek "a végéig". A modern programok kezdeti állapotúak, kilépésük után események fordulnak elő bennük - kiszámíthatatlan sorrendben, változó időintervallumokkal. A kód egyszálú marad, de már aszinkron lesz. Az adatvesztés valószínűsége éppen azért nő, mert az események bekövetkezési sorrendje nagyon fontos. Az ilyen helyzetek meglehetősen gyakoriak: ha a B esemény az A esemény után következik be, akkor minden jól működik. De ha az A esemény a B esemény után következik be, és a C eseménynek van ideje beavatkozni közöttük, akkor az adatok felismerhetetlenül torzulhatnak.

Ha párhuzamos folyamokról van szó, a probléma tovább súlyosbodik, mivel több módszer is működhet egyszerre globális állapotban. Lehetetlen megítélni, hogy pontosan hogyan változik a globális állapot. Már nemcsak arról beszélünk, hogy az események előre nem látható sorrendben fordulhatnak elő, hanem arról is, hogy több végrehajtási szál állapota frissíthető. egyidejűleg... Az aszinkron programozással legalább biztosíthatja, hogy egy bizonyos esemény ne történhessen meg, mielőtt egy másik esemény feldolgozása befejeződik. Vagyis biztonsággal meg lehet mondani, hogy mi lesz a globális állapot egy adott esemény feldolgozásának végén. A többszálas kódban általában nem lehet megmondani, hogy mely események fognak párhuzamosan bekövetkezni, így lehetetlen biztonsággal leírni a globális állapotot az idő bármely pillanatában.

A többszálas program, amely kiterjedt, globálisan megváltoztatható állapotú, az egyik legbeszédesebb példa Heisenberg bizonytalanság elvére, amelyet ismerek. Lehetetlen ellenőrizni egy program állapotát a viselkedésének megváltoztatása nélkül.

Amikor elkezdek egy újabb filozófiát a globális megváltoztatható állapotról (a lényeget az előző néhány bekezdés vázolja fel), a programozók lesütik a szemüket, és biztosítják, hogy mindezt már régóta tudják. De ha ezt tudja, miért nem tudja megállapítani a kódjából? A programok tele vannak globálisan megváltoztatható állapotokkal, és a programozók azon tűnődnek, miért nem működik a kód.

Nem meglepő, hogy a többszálas programozás legfontosabb munkája a tervezési fázisban történik. Világosan meg kell határozni, hogy a programnak mit kell tennie, független modulokat kell kifejlesztenie az összes funkció végrehajtásához, részletesen le kell írnia, hogy melyik modulhoz milyen adatok szükségesek, és meg kell határoznia a modulok közötti információcsere módjait ( Igen, ne felejtsen el szép pólókat készíteni mindenkinek, aki részt vesz a projektben. Első dolog.- kb. szerk. eredetiben). Ez a folyamat alapvetően nem különbözik az egyszálú program tervezésétől. A siker kulcsa, mint az egyszálú kód esetében, a modulok közötti interakció korlátozása. Ha megszabadulhat a megosztott, megváltoztatható állapottól, az adatmegosztási problémák egyszerűen nem merülnek fel.

Valaki azzal érvelhet, hogy néha nincs idő a program ilyen kényes kialakítására, ami lehetővé teszi a globális állam nélkül való megélést. Úgy gondolom, hogy lehetséges és szükséges időt fordítani erre. A többszálas programokat semmi sem befolyásolja olyan rombolóan, mint a globális változékony állapotokkal való megküzdés. Minél részletesebben kell kezelnie, annál valószínűbb, hogy a program csúcspontja lesz és összeomlik.

A reális alkalmazásokban bizonyos közös állapotnak kell lennie, amely változhat. És itt kezdődnek a legtöbb programozó problémái. A programozó látja, hogy itt megosztott állapotra van szükség, a többszálas arzenálhoz fordul, és onnan veszi a legegyszerűbb eszközt: egy univerzális zárat (kritikus rész, mutex, vagy ahogy hívják). Úgy gondolják, hogy a kölcsönös kizárás megold minden adatmegosztási problémát.

Az egyetlen zárral felmerülő problémák száma megdöbbentő. Figyelembe kell venni a versenykörülményeket, a túlzott mértékű blokkolással járó áthidalási problémákat, és az elosztás igazságossági kérdései csak néhány példa. Ha több zárja van, különösen, ha egymásba vannak ágyazva, akkor intézkedéseket kell tennie a holtpont, a dinamikus zárolás, a blokkolási sorok és az egyidejűséggel kapcsolatos egyéb fenyegetések ellen is. Ezenkívül vannak egyedi blokkolási problémák is.
Amikor kódot írok vagy felülvizsgálok, szinte tévedhetetlen vasszabályom van: ha zárat csinált, úgy tűnik, valahol hibát követett el.

Ezt az állítást kétféleképpen lehet kommentálni:

  1. Ha zárolásra van szüksége, akkor valószínűleg globális, módosítható állapota van, amelyet védeni szeretne az egyidejű frissítésekkel szemben. A globális mutálható állapot jelenléte hiba az alkalmazás tervezési fázisában. Felülvizsgálat és újratervezés.
  2. A zárak helyes használata nem könnyű, és hihetetlenül nehéz lehet a zárolással kapcsolatos hibák lokalizálása. Nagyon valószínű, hogy nem megfelelően fogja használni a zárat. Ha zárat látok, és a program szokatlan módon viselkedik, akkor először meg kell vizsgálnom a zártól függő kódot. És általában problémákat találok benne.

Mindkét értelmezés helyes.

A többszálas kód írása egyszerű. De nagyon -nagyon nehéz helyesen használni a szinkronizációs primitíveket. Lehet, hogy nem vagy képes egyetlen zár helyes használatára sem. Végül is a zárak és más szinkronizációs primitívek olyan konstrukciók, amelyeket a teljes rendszer szintjén állítanak fel. Azok az emberek, akik nálad sokkal jobban értik a párhuzamos programozást, ezeket a primitíveket használják egyidejű adatszerkezetek és magas szintű szinkronizálási konstrukciók létrehozására. És te és én, hétköznapi programozók, csak vegyük az ilyen konstrukciókat és használjuk őket a kódunkban. Egy alkalmazásprogramozónak nem szabad gyakrabban használnia alacsony szintű szinkronizálási primitíveket, mint ahogy közvetlen hívásokat kezdeményez az eszközillesztőkhöz. Vagyis szinte soha.

A zárak használata az adatmegosztási problémák megoldására olyan, mint a tüzet oltani folyékony oxigénnel. Mint a tűz, az ilyen problémákat könnyebb megelőzni, mint kijavítani. Ha megszabadul a megosztott állapottól, akkor sem kell visszaélnie a szinkronizációs primitívekkel.

A legtöbb, amit a többszálúságról tud, lényegtelen

A kezdőknek szóló többszálas oktatóanyagokban megtudhatja, mi a szál. Ezután a szerző elkezdi mérlegelni, hogy ezek a szálak hogyan működhetnek párhuzamosan - például beszéljen a megosztott adatokhoz való hozzáférés zárak és szemaforok használatával történő ellenőrzéséről, és foglalkozzon azzal, hogy mi történhet az eseményekkel való munka során. Alaposan megvizsgálja az állapotváltozókat, a memória akadályokat, a kritikus szakaszokat, a mutexeket, az illékony mezőket és az atomi műveleteket. Példák kerülnek bemutatásra, hogyan lehet ezeket az alacsony szintű konstrukciókat használni mindenféle rendszerművelet végrehajtásához. Miután félig elolvasta ezt az anyagot, a programozó úgy dönt, hogy már eleget tud ezekről a primitívekről és azok használatáról. Végül is, ha tudom, hogyan működik ez a dolog rendszerszinten, akkor alkalmazhatom ugyanazt az alkalmazás szintjén. Igen?

Képzelje el, hogy egy tinédzsernek elmondja, hogyan kell saját maga összeállítani egy belsőégésű motort. Aztán minden vezetési képzés nélkül beülteted az autó volánja mögé, és azt mondod: "Menj!" A tinédzser megérti az autó működését, de fogalma sincs, hogyan lehet rajta eljutni A pontból B pontba.

A szálak rendszerszintű működésének megértése általában semmiben sem segít alkalmazásszinten. Nem azt javaslom, hogy a programozóknak ne kelljen megtanulniuk ezeket az alacsony szintű részleteket. Egy üzleti alkalmazás tervezésekor vagy fejlesztésekor ne számítsunk arra, hogy ezt a tudást rögtön alkalmazni tudjuk.

A bevezető fonalas irodalom (és a kapcsolódó tudományos kurzusok) nem vizsgálhatja az ilyen alacsony szintű konstrukciókat. Összpontosítania kell a leggyakoribb problémaosztályok megoldására, és meg kell mutatnia a fejlesztőknek, hogyan oldják meg ezeket a problémákat magas szintű képességek használatával. Elvileg a legtöbb üzleti alkalmazás rendkívül egyszerű program. Adatokat olvasnak be egy vagy több beviteli eszközről, elvégeznek néhány bonyolult feldolgozást ezen adatokon (például a folyamat során további adatokat kérnek), majd kiadják az eredményeket.

Az ilyen programok gyakran tökéletesen illeszkednek a szolgáltató-fogyasztó modellbe, amely csak három szálat igényel:

  • a bemeneti adatfolyam beolvassa az adatokat, és a beviteli sorba helyezi őket;
  • a dolgozó szál kiolvassa a rekordokat a beviteli sorból, feldolgozza azokat, és az eredményeket a kimeneti sorba helyezi;
  • a kimeneti adatfolyam kiolvassa a bejegyzéseket a kimeneti sorból és tárolja azokat.

Ez a három szál önállóan működik, a kommunikáció a sorok szintjén történik.

Bár technikailag ezeket a sorokat közös állapotú zónáknak lehet tekinteni, a gyakorlatban ezek csak kommunikációs csatornák, amelyekben saját belső szinkronizációjuk működik. A várólisták támogatják a sok termelővel és fogyasztóval való együttműködést, párhuzamosan hozzáadhat és eltávolíthat elemeket belőlük.

Mivel a bemeneti, feldolgozási és kimeneti szakaszok elszigeteltek egymástól, a végrehajtásuk könnyen megváltoztatható anélkül, hogy befolyásolná a program többi részét. Amíg a sorban lévő adatok típusa nem változik, saját belátása szerint átalakíthatja az egyes programösszetevőket. Ezenkívül, mivel tetszőleges számú beszállító és fogyasztó vesz részt a sorban, nem nehéz más gyártókat / fogyasztókat hozzáadni. Több tucat bemeneti adatfolyamunk írhat információkat ugyanabba a sorba, vagy tucatnyi dolgozói szál veszi az információkat a bemeneti sorból, és feldolgozza az adatokat. Egyetlen számítógép keretein belül egy ilyen modell jól skálázható.

A legfontosabb, hogy a modern programozási nyelvek és könyvtárak nagyon megkönnyítik a termelő-fogyasztó alkalmazások létrehozását. A .NET -ben megtalálhatja a Párhuzamos gyűjteményeket és a TPL Dataflow Library -t. A Java rendelkezik a Executor szolgáltatással, valamint a BlockingQueue és más osztályokkal a java.util.concurrent névtérből. A C ++ rendelkezik Boost szálkönyvtárral és az Intel Thread Building Blocks könyvtárával. A Microsoft Visual Studio 2013 aszinkron ügynököket vezet be. Hasonló könyvtárak elérhetők Python, JavaScript, Ruby, PHP és ha jól tudom sok más nyelven is. Létrehozhat termelő-fogyasztó alkalmazást ezen csomagok bármelyikével, anélkül, hogy valaha zárakhoz, szemaforokhoz, állapotváltozókhoz vagy más szinkronizálási primitívekhez kellene folyamodnia.

A szinkronizálási primitívek széles választékát szabadon használják ezekben a könyvtárakban. Ez jó. Mindezeket a könyvtárakat olyan emberek írják, akik összehasonlíthatatlanul jobban értik a többszálúságot, mint egy átlagos programozó. Egy ilyen könyvtárral való munkavégzés gyakorlatilag megegyezik a futásidejű nyelvi könyvtár használatával. Ezt össze lehet hasonlítani a programozással magas szintű nyelven, nem pedig összeszerelési nyelven.

A beszállítói-fogyasztói modell csak egy példa a sok közül. A fenti könyvtárak olyan osztályokat tartalmaznak, amelyek felhasználhatók sok közös menetvázlat-tervezési minta megvalósítására anélkül, hogy mélyreható részletekbe mennének. Lehetőség van nagyméretű, többszálú alkalmazások létrehozására anélkül, hogy aggódnia kellene a szálak összehangolása és szinkronizálása miatt.

Munka a könyvtárakkal

Tehát a többszálas programok létrehozása alapvetően nem különbözik az egyszálú szinkron programok írásától. A beágyazás és az adatok elrejtésének fontos elvei egyetemesek, és csak akkor nőnek fontosságuk, ha több egyidejű szál is érintett. Ha figyelmen kívül hagyja ezeket a fontos szempontokat, akkor még az alacsony szintű menetek legátfogóbb ismerete sem fog megmenteni.

A modern fejlesztőknek sok problémát kell megoldaniuk az alkalmazásprogramozás szintjén, előfordul, hogy egyszerűen nincs idő gondolkodni azon, hogy mi történik a rendszer szintjén. Minél bonyolultabbak az alkalmazások, annál bonyolultabb részleteket kell elrejteni az API -szintek között. Több mint egy tucat éve csináljuk ezt. Vitatható, hogy a rendszer összetettségének minőségi elrejtése a programozó elől a fő oka annak, hogy a programozó képes modern alkalmazások írására. Ami azt illeti, nem titkoljuk el a rendszer összetettségét az UI üzenethurok megvalósításával, alacsony szintű kommunikációs protokollok építésével stb.?

Hasonló a helyzet a többszálúsággal. A legtöbb, többszálas forgatókönyv, amellyel egy átlagos üzleti alkalmazásprogramozó találkozhat, már jól ismert és jól megvalósult a könyvtárakban. A könyvtári funkciók remekül elrejtik a párhuzamosság elsöprő bonyolultságát. Meg kell tanulnia, hogyan kell ezeket a könyvtárakat ugyanúgy használni, mint a felhasználói felület elemeit, kommunikációs protokolljait és számos más, egyszerűen működő eszköztárat. Hagyja az alacsony szintű többszálat a szakemberekre - az alkalmazások létrehozásakor használt könyvtárak szerzőire.

NS Ez a cikk nem a tapasztalt Python szelídítőknek szól, akiknek a kígyógömb kibontása gyerekjáték, hanem felületes áttekintés az újonnan függő python többszálú képességeiről.

Sajnos a Python -ban többszálúság témakörben nincs olyan sok orosz nyelvű anyag, és olyan pythonosok, akik semmit sem hallottak, például a GIL -ről, irigylésre méltó rendszerességgel kezdtek szembe jönni velem. Ebben a cikkben megpróbálom leírni a többszálú python legalapvetőbb tulajdonságait, elmondom, mi a GIL és hogyan kell vele együtt élni (vagy anélkül), és még sok más.


A Python egy bájos programozási nyelv. Tökéletesen ötvözi számos programozási paradigmát. A legtöbb feladat, amellyel egy programozó találkozhat, itt könnyen, elegánsan és tömören megoldható. De ezekre a problémákra gyakran elegendő egyszálú megoldás, és az egyszálú programok általában előre megjósolhatók és könnyen hibakereshetők. Ugyanez nem mondható el a többszálas és többprocesszoros programokról.

Többszálas alkalmazások


A Python rendelkezik modullal menetvágás , és mindent tartalmaz, ami a többszálas programozáshoz szükséges: különféle típusú zárak, szemafor és eseménymechanizmus létezik. Egy szóval - minden, ami a többszálas programok túlnyomó többségéhez szükséges. Ezenkívül ezen eszközök használata meglehetősen egyszerű. Tekintsünk egy példát egy olyan programra, amely 2 szálat indít. Az egyik szál tíz "0" -t ír, a másik tíz "1" -t és szigorúan sorra.

import szálazás

def író

i xrange -ben (10):

nyomtatás x

Event_for_set.set ()

# init események

e1 = menetvágás. Esemény ()

e2 = menetvágás. Esemény ()

# init szál

0, e1, e2))

1, e2, e1))

# szálak indítása

t1.start ()

t2.start ()

t1.csatlakozz ()

t2.csatlakozz ()


Nincs varázslat vagy voodoo kód. A kód világos és következetes. Ezenkívül, amint láthatja, egy függvényből létrehoztunk egy adatfolyamot. Ez nagyon kényelmes kis feladatokhoz. Ez a kód is elég rugalmas. Tegyük fel, hogy van egy harmadik folyamatunk, amely „2” -t ír, akkor a kód így fog kinézni:

import szálazás

def író (x, event_for_wait, event_for_set):

i xrange -ben (10):

Event_for_wait.wait () # várjon az eseményre

Event_for_wait.clear () # tiszta esemény a jövőre nézve

nyomtatás x

Event_for_set.set () # esemény beállítása a szomszéd szálhoz

# init események

e1 = menetvágás. Esemény ()

e2 = menetvágás. Esemény ()

e3 = menetvágás. Esemény ()

# init szál

t1 = menetvágás. Szál (cél = író, args = ( 0, e1, e2))

t2 = menetvágás. Szál (cél = író, args = ( 1, e2, e3))

t3 = menetvágás. Szál (cél = író, args = ( 2, e3, e1))

# szálak indítása

t1.start ()

t2.start ()

t3.tart ()

e1.set () # kezdeményezi az első eseményt

# csatlakoztassa a szálakat a fő szálhoz

t1.csatlakozz ()

t2.csatlakozz ()

t3.csatlakozz ()


Hozzáadtunk egy új eseményt, egy új szálat, és kissé megváltoztattuk a paramétereket
folyamok indulnak el (természetesen általánosabb megoldást is írhat például a MapReduce használatával, de ez túlmutat a cikk keretein).
Mint láthatja, még mindig nincs varázslat. Minden egyszerű és egyértelmű. Menjünk tovább.

Globális tolmácszár


A szálak használatának két leggyakoribb oka van: először is, hogy növeljük a modern processzorok többmagos architektúrájának használatának hatékonyságát, és ezáltal a program teljesítményét;
másodszor, ha a program logikáját párhuzamos, teljesen vagy részben aszinkron szakaszokra kell osztanunk (például, hogy egyszerre több szervert is pingálhassunk).

Az első esetben a Python (vagy inkább annak fő CPython -megvalósítása) olyan korlátozásával kell szembenéznünk, mint a Global Interpreter Lock (vagy röviden GIL). A GIL koncepciója szerint egy processzor egyszerre csak egy szálat tud végrehajtani. Ez azért történik, hogy ne legyen küzdelem a szálak között a külön változókért. A futtatható szál hozzáférést biztosít a teljes környezethez. A Python szál megvalósításának ez a jellemzője nagyban leegyszerűsíti a szálakkal végzett munkát, és bizonyos szálbiztonságot biztosít.

Van azonban egy finom pont: úgy tűnhet, hogy egy többszálú alkalmazás pontosan ugyanannyi ideig fog futni, mint az egyszálú, ugyanezt végző alkalmazás, vagy az egyes szálak végrehajtási idejének összege a CPU-n. De itt egy kellemetlen hatás vár ránk. Tekintsük a programot:

open ("test1.txt", "w"), mint fout:

i esetén xrange (1000000):

nyomtatás >> fout, 1


Ez a program csak millió sort „1” ír egy fájlba, és ~ 0,35 másodperc alatt elvégzi a számítógépemen.

Fontolja meg egy másik programot:

menetes importálásból Szál

def író (fájlnév, n):

nyitva (fájlnév, "w"), mint fout:

i esetén xrange (n):

nyomtatás >> fout, 1

t1 = Szál (cél = író, args = ("test2.txt", 500000,))

t2 = Szál (cél = író, args = ("test3.txt", 500000,))

t1.start ()

t2.start ()

t1.csatlakozz ()

t2.csatlakozz ()


Ez a program 2 szálat hoz létre. Minden szálban külön fájlba ír, félmillió sor "1". Valójában a munka mennyisége megegyezik az előző programmal. De idővel itt érdekes hatást érünk el. A program 0,7 másodperctől akár 7 másodpercig is futhat. Miért történik ez?

Ez annak a ténynek köszönhető, hogy amikor egy szálnak nincs szüksége CPU erőforrásra, felszabadítja a GIL -t, és ebben a pillanatban megpróbálhatja megszerezni azt, és egy másik szálat, valamint a fő szálat. Ugyanakkor az operációs rendszer, tudva, hogy sok mag van, mindent súlyosbíthat, ha szálakat próbál elosztani a magok között.

UPD: Jelenleg a Python 3.2 -ben a GIL továbbfejlesztett megvalósítása létezik, amelyben ez a probléma részben megoldott, különösen azért, mert minden szál, miután elveszítette az irányítást, rövid ideig vár előtte ismét el tudja készíteni a GIL -t (jó angol nyelvű prezentáció)

„Tehát nem tud hatékony többszálas programokat írni a Pythonba?” Kérdezi. Nem, természetesen van kiút, sőt több is.

Többprocesszoros alkalmazások


Annak érdekében, hogy bizonyos értelemben megoldja az előző bekezdésben leírt problémát, a Python rendelkezik egy modullal alfolyamat ... Írhatunk egy programot, amelyet egy párhuzamos szálon szeretnénk végrehajtani (sőt, már folyamat). És futtassa egy vagy több szálban egy másik programban. Ez valóban felgyorsítaná a programunkat, mert a GIL indítóban létrehozott szálak nem veszik fel, hanem csak várják a futási folyamat befejezését. Ennek a módszernek azonban sok problémája van. A fő probléma az, hogy nehézkessé válik az adatok átvitele a folyamatok között. Valahogy soroznia kell az objektumokat, kommunikációt kell létesítenie PIPE -n vagy más eszközökön keresztül, de mindez óhatatlanul túlterhelt, és a kód nehezen érthetővé válik.

Itt egy másik megközelítés segíthet nekünk. A Python többprocesszoros modullal rendelkezik ... A funkcionalitás szempontjából ez a modul hasonlít menetvágás ... Például a folyamatok ugyanúgy létrehozhatók a szokásos függvényekből. A folyamatokkal való munkavégzés módszerei majdnem ugyanazok, mint a menetvágó modul szálainál. A folyamatok és az adatcsere szinkronizálásához azonban szokás más eszközöket használni. Sorokról (Queue) és csövekről (Pipe) beszélünk. Azonban a zárolás, események és szemaforok analógjai, amelyek menetben voltak, szintén itt vannak.

Ezenkívül a többprocesszoros modul rendelkezik mechanizmussal a megosztott memóriával való munkavégzéshez. Ehhez a modulnak van egy változó (Value) és egy tömb (Array) osztálya, amelyek „megoszthatók” a folyamatok között. A megosztott változókkal való munka megkönnyítése érdekében használhatja a kezelői osztályokat. Rugalmasabbak és könnyebben használhatók, de lassabbak. Meg kell jegyezni, hogy van egy jó lehetőség arra, hogy a ctypes modulból gyakori típusokat készítsen a multiprocessing.sharedctypes modul segítségével.

A többprocesszoros modulban is van egy folyamatkészletek létrehozására szolgáló mechanizmus. Ez a mechanizmus nagyon kényelmesen használható a Mester-Munkás minta megvalósításához vagy egy párhuzamos Térkép megvalósításához (ami bizonyos értelemben a Mester-Munkás speciális esete).

A többprocesszoros modullal való munkavégzés fő problémái közül érdemes megjegyezni ennek a modulnak a relatív platformfüggőségét. Mivel a folyamatokkal való munka különböző operációs rendszerekben eltérő módon van megszervezve, bizonyos korlátozások vannak érvényben a kódra vonatkozóan. Például a Windows nem rendelkezik villamechanizmussal, ezért a folyamatleválasztási pontot be kell csomagolni:

ha __név__ == "__fő__":


Ez a kialakítás azonban már jó forma.

Mi más...


Vannak más könyvtárak és módszerek a Pythonban párhuzamos alkalmazások írására. Használhat például Hadoop + Python -t vagy különféle Python MPI -implementációkat (pyMPI, mpi4py). Akár a meglévő C ++ vagy Fortran könyvtárak burkolóit is használhatja. Itt említhetnénk olyan kereteket / könyvtárakat, mint a Pyro, Twisted, Tornado és még sokan mások. De mindez már túlmutat e cikk keretein.

Ha tetszett a stílusom, akkor a következő cikkben megpróbálom elmondani, hogyan kell egyszerű tolmácsokat írni a PLY -ben és mire használhatók.

10. fejezet.

Többszálas alkalmazások

A multitasking a modern operációs rendszerekben magától értetődő [ Az Apple OS X megjelenése előtt a Macintosh számítógépeken nem voltak modern multitasking operációs rendszerek. Nagyon nehéz megfelelően tervezni az operációs rendszert teljes multitasking funkcióval, ezért az OS X -et a Unixra kellett alapozni.]. A felhasználó elvárja, hogy a szövegszerkesztő és a levelezőprogram egyidejű elindításakor ezek a programok ne ütközjenek egymással, és e-mailek fogadásakor a szerkesztő nem áll le. Ha egyszerre több programot indítanak el, akkor az operációs rendszer gyorsan vált a programok között, és egy processzort biztosít számukra (kivéve persze, ha több processzor van telepítve a számítógépre). Ennek eredményeként illúzió több program egyidejű futtatása, mert még a legjobb gépíró (és a leggyorsabb internetkapcsolat) sem tud lépést tartani a modern processzorral.

A többszálúság bizonyos értelemben a multitasking következő szintjének tekinthető: ahelyett, hogy váltanánk a különbözőek között programok, az operációs rendszer ugyanazon program különböző részei között vált. Például egy többszálas e-mail kliens lehetővé teszi új e-mailek fogadását olvasás vagy új üzenetek írása közben. Manapság a többszálúságot is sok felhasználó természetesnek veszi.

A VB -nek soha nem volt normál többszálú támogatása. Igaz, egyik fajtája megjelent a VB5 -ben - együttműködési streaming modell(lakásmenetes). Amint azt hamarosan látni fogja, az együttműködési modell a programozó számára nyújtja a többszálúság néhány előnyét, de nem használja ki teljes mértékben az összes szolgáltatást. Előbb vagy utóbb át kell váltania egy edzőgépről egy valódi gépre, és a VB .NET lett az első VB verzió, amely támogatja az ingyenes többszálas modellt.

A többszálúság azonban nem tartozik a programozási nyelveken könnyen megvalósítható és a programozók által könnyen elsajátítható szolgáltatások közé. Miért?

Mivel többszálú alkalmazásokban nagyon trükkös hibák fordulhatnak elő, amelyek kiszámíthatatlanul jelennek meg és tűnnek el (és az ilyen hibákat a legnehezebb elhárítani).

Őszintén figyelmeztetve: a többszálú programozás az egyik legnehezebb programozási terület. A legkisebb figyelmetlenség megfoghatatlan hibák megjelenéséhez vezet, amelyek javítása csillagászati ​​összegeket igényel. Ezért ez a fejezet sok mindent tartalmaz rossz példák - szándékosan úgy írtuk őket, hogy bizonyítsák a gyakori hibákat. Ez a legbiztonságosabb megközelítés a többszálas programozás elsajátításához: képesnek kell lennie észlelni a lehetséges problémákat, amikor első pillantásra minden jól működik, és tudnia kell, hogyan kell azokat megoldani. Ha többszálas programozási technikákat szeretne használni, nem teheti meg nélküle.

Ez a fejezet szilárd alapot fog képezni a további önálló munkához, de nem fogjuk tudni leírni a többszálas programozást minden bonyolultságban - csak a Threading névtér osztályaira vonatkozó nyomtatott dokumentáció több mint 100 oldalt vesz igénybe. Ha magasabb szinten szeretné elsajátítani a többszálas programozást, olvassa el a szakkönyveket.

De bármilyen veszélyes is a többszálas programozás, elengedhetetlen néhány probléma professzionális megoldásához. Ha a programok nem használnak többszálasítást, ahol szükséges, a felhasználók nagyon csalódottak lesznek, és inkább egy másik terméket részesítenek előnyben. Például csak a népszerű e-mail program negyedik változatában jelent meg az Eudora többszálas képessége, amely nélkül lehetetlen elképzelni egy modern programot az e-mailekkel való munkavégzéshez. Mire az Eudora bevezette a többszálas támogatást, sok felhasználó (köztük a könyv egyik szerzője) más termékekre váltott.

Végül .NET-ben az egyszálú programok egyszerűen nem léteznek. Minden A .NET programok többszálúak, mert a szemétszedő alacsony prioritású háttérfolyamatként fut. Amint az alábbiakban látható, komoly .NET grafikus programozás esetén a megfelelő szálazás megakadályozhatja a grafikus felület blokkolását, amikor a program hosszadalmas műveleteket hajt végre.

A többszálúság bemutatása

Minden program egy meghatározott módon működik kontextus, leírja a kód és az adatok memóriában való eloszlását. A kontextus mentésével a programfolyamat állapota ténylegesen mentésre kerül, ami lehetővé teszi a jövőbeni visszaállítását és a program végrehajtásának folytatását.

A kontextus megtakarítása időt és memóriát igényel. Az operációs rendszer megjegyzi a programszál állapotát, és átadja a vezérlést egy másik szálnak. Amikor a program folytatni kívánja a felfüggesztett szál végrehajtását, akkor a mentett környezetet vissza kell állítani, ami még tovább tart. Ezért a többszálasítást csak akkor szabad használni, ha az előnyök ellensúlyozzák az összes költséget. Az alábbiakban felsorolunk néhány tipikus példát.

  • A program funkcionalitása egyértelműen és természetesen több heterogén műveletre oszlik, mint például az e-mailek fogadása és az új üzenetek előkészítése esetén.
  • A program hosszú és összetett számításokat végez, és nem szeretné, hogy a grafikus felület blokkolva legyen a számítások idejére.
  • A program többprocesszoros számítógépen fut, amelynek operációs rendszere több processzor használatát támogatja (amíg az aktív szálak száma nem haladja meg a processzorok számát, a párhuzamos végrehajtás gyakorlatilag mentes a szálak váltásával járó költségektől).

Mielőtt továbblépnénk a többszálas programok mechanikájához, ki kell emelnünk egy olyan körülményt, amely gyakran zavart okoz a kezdők körében a többszálas programozás területén.

A programfolyamatban nem eljárást, hanem eljárást hajtanak végre.

Nehéz megmondani, mit jelent az "objektum végrehajtása" kifejezés, de az egyik szerző gyakran tart szemináriumokat a többszálas programozásról, és ezt a kérdést gyakrabban teszik fel, mint mások. Talán valaki azt gondolja, hogy a programszál munkája az osztály Új metódusának hívásával kezdődik, majd a szál feldolgozza a megfelelő objektumnak továbbított összes üzenetet. Ilyen reprezentációk teljesen nem helyesek. Egy objektum több szálat tartalmazhat, amelyek különböző (és néha még azonos) módszereket hajtanak végre, míg az objektum üzeneteit több különböző szál továbbítja és fogadja (mellesleg ez az egyik oka a többszálas programozásnak: egy program hibakereséséhez meg kell találnia, hogy egy adott pillanatban melyik szál hajtja végre ezt vagy azt az eljárást!).

Mivel a szálak objektumok metódusaiból jönnek létre, maga az objektum általában a szál előtt jön létre. Az objektum sikeres létrehozása után a program létrehoz egy szálat, átadva az objektum metódusának címét, és csak ezután parancsot ad a szál végrehajtásának megkezdésére. Az eljárás, amelyhez a szálat létrehozták, mint minden eljárás, új objektumokat hozhat létre, műveleteket hajthat végre a meglévő objektumokon, és meghívhat más eljárásokat és funkciókat, amelyek a hatókörébe tartoznak.

Az osztályok általános módszerei programszálakban is végrehajthatók. Ebben az esetben ne feledje egy másik fontos körülményt sem: a szál az eljárásból való kilépéssel végződik, amelyhez létrehozták. A programfolyamat normál befejezése addig nem lehetséges, amíg az eljárásból kilép.

A szálak nemcsak természetes módon, hanem rendellenesen is leállhatnak. Ez általában nem ajánlott. További információért lásd: Az adatfolyamok befejezése és megszakítása.

A programszálak használatához kapcsolódó alapvető .NET eszközök a Threading névtérben koncentrálódnak. Ezért a legtöbb többszálú programnak a következő sorral kell kezdődnie:

Importálási rendszer

A névtér importálása megkönnyíti a program gépelését, és lehetővé teszi az IntelliSense technológiát.

Az áramlások közvetlen kapcsolata az eljárásokkal azt sugallja, hogy ezen a képen, küldöttek(lásd a 6. fejezetet). Pontosabban, a Threading névtér tartalmazza a ThreadStart delegáltot, amelyet általában a programszálak indításakor használnak. A küldött használatának szintaxisa így néz ki:

Nyilvános delegált alszál (Start) ()

A ThreadStart felhatalmazottal meghívott kódnak nem lehet paramétere vagy visszatérési értéke, ezért nem hozhatók létre szálak a függvényekhez (amelyek értéket adnak vissza) és a paraméterekkel rendelkező eljárásokhoz. Az adatfolyamból történő adatátvitelhez alternatív eszközöket is keresnie kell, mivel a végrehajtott metódusok nem adnak vissza értékeket, és nem használhatják az utalás útján történő továbbítást. Például, ha a ThreadMethod a WilluseThread osztályba tartozik, akkor a ThreadMethod a WillUseThread osztály példányainak tulajdonságainak módosításával tud információt közölni.

Alkalmazási tartományok

A .NET szálak úgynevezett alkalmazástartományokban futnak, a dokumentációban "a homokozó, amelyben az alkalmazás fut". Az alkalmazástartományt a Win32 folyamatok könnyű verziójának tekinthetjük; egyetlen Win32 folyamat több alkalmazástartományt is tartalmazhat. A fő különbség az alkalmazástartományok és a folyamatok között az, hogy a Win32 folyamatnak saját címtere van (a dokumentációban az alkalmazástartományokat is összehasonlítják a fizikai folyamaton belül futó logikai folyamatokkal). A NET -ben az összes memóriakezelést a futási idő kezeli, így több alkalmazástartomány futhat egyetlen Win32 folyamatban. Ennek a rendszernek az egyik előnye az alkalmazások jobb méretezési képessége. Az alkalmazástartományokkal való munka eszközei az AppDomain osztályba tartoznak. Javasoljuk, hogy tanulmányozza az osztály dokumentációját. Segítségével információkat szerezhet arról a környezetről, amelyben a program fut. Különösen az AppDomain osztályt használják a .NET rendszerosztályok reflexiója során. A következő program felsorolja a betöltött szerelvényeket.

Import rendszer. Reflexió

Modul modul

Sub Main ()

A tartomány dimenziója AppDomainként

theDomain = AppDomain.CurrentDomain

Dim Assemblies () As

Összeszerelések = theDomain.GetAssemblies

Dim és ÖsszeszerelésxAs

Minden egyes összeszereléshez a szerelvényekben

Console.WriteLinetanAssemble.Full Name) Következő

Console.ReadLine ()

End Sub

Vége modul

Folyamatok létrehozása

Kezdjük egy kezdetleges példával. Tegyük fel, hogy egy eljárást szeretne futtatni egy külön szálban, amely csökkenti a számláló értékét egy végtelen ciklusban. Az eljárást az osztály részeként határozzák meg:

Nyilvános osztály WillUseThreads

Nyilvános kivonásFromCounter ()

Halvány egésznek számít

Do while True count - = 1

Console.WriteLlne ("Egy másik szálban vagyok és számláló ="

& számol)

Hurok

End Sub

Vége az osztálynak

Mivel a Do hurok feltétel mindig igaz, azt gondolhatja, hogy semmi sem zavarja a SubtractFromCounter eljárást. Egy többszálú alkalmazásban azonban ez nem mindig van így.

A következő részlet bemutatja a szál indítását végző Sub Main eljárást és az Importálás parancsot:

Opció Szigorúan az importálási rendszerben. Threading Module Modulel

Sub Main ()

1 Dim myTest mint új WillUseThreads ()

2 Dim bThreadStart mint új ThreadStart (AddressOf _

myTest.SubtractFromCounter)

3 Dim bThread mint új szál (bThreadStart)

4 "bThread.Start ()

Dim i As Integer

5 Tedd, amíg igaz

Console.WriteLine ("A fő szálban és a szám" & i) i + = 1

Hurok

End Sub

Vége modul

Nézzük sorban a legfontosabb pontokat. Először is, a Sub Man n eljárás mindig működik fő folyam(fő szál). A .NET programokban mindig legalább két szál fut: a főszál és a szemétgyűjtő szál. Az 1. sor a tesztosztály új példányát hozza létre. A 2. sorban létrehozunk egy ThreadStart delegáltot, és átadjuk a SubtractFromCounter eljárás címét az 1. sorban létrehozott tesztosztály -példánynak (ezt az eljárást paraméterek nélkül hívják). JóA Threading névtér importálásával a hosszú név elhagyható. Az új szálobjektum a 3. sorban jön létre. Figyelje meg a ThreadStart küldött elhaladását a Thread osztály konstruktorának hívásakor. Néhány programozó inkább ezt a két sort kapcsolja össze egy logikai sorba:

Dim bThread mint új szál (New ThreadStarttAddressOf _

myTest.SubtractFromCounter))

Végül a 4. sor "elindítja" a szálat a ThreadStart delegált számára létrehozott Thread példány Start metódusának meghívásával. Ennek a módszernek a meghívásával azt mondjuk az operációs rendszernek, hogy a Kivonás eljárást külön szálon kell futtatni.

Az előző bekezdésben az "indul" szó idézőjelbe van foglalva, mert ez a sokszálas programozás sok furcsasága közül az egyik: A Start hívása valójában nem indítja el a szálat! Csak azt mondja az operációs rendszernek, hogy ütemezze a megadott szál futását, de a program nem befolyásolja a közvetlen indítást. Ön nem tudja elkezdeni a szálak végrehajtását, mert az operációs rendszer mindig szabályozza a szálak végrehajtását. Egy későbbi részben megtanulhatja, hogyan használhatja a prioritást, hogy az operációs rendszer gyorsabban indítsa el a szálat.

Ábrán. A 10.1 példát mutat arra, hogy mi történhet egy program indítása, majd megszakítása után a Ctrl + Break gombbal. Esetünkben csak akkor indult új szál, miután a főszál számlálója 341 -re nőtt!

Rizs. 10.1. Egyszerű többszálas szoftver futásideje

Ha a program hosszabb ideig fut, az eredmény valami olyasmi lesz, mint az ábrán látható. 10.2. Látjuk, hogy tea futó szál befejezése felfüggesztésre kerül, és a vezérlés ismét átkerül a főszálra. Ebben az esetben van egy megnyilvánulás megelőző többszálú időszeletelés. Ennek a félelmetes kifejezésnek a jelentését az alábbiakban ismertetjük.

Rizs. 10.2. Váltás a szálak között egy egyszerű többszálas programban

Amikor megszakítja a szálakat és átadja a vezérlést más szálaknak, az operációs rendszer a megelőző többszálúság elvét használja az időszeletelésen keresztül. Az időkvantálás megoldja az egyik gyakori problémát is, amely korábban felmerült a többszálas programokban - egy szál veszi fel a teljes CPU -időt, és nem marad el a többi szál vezérlésétől (ez általában a fentihez hasonló intenzív ciklusokban történik). A kizárólagos CPU -eltérítés elkerülése érdekében a szálaknak időről időre át kell adniuk az irányítást más szálaknak. Ha a program "öntudatlannak" bizonyul, van egy másik, valamivel kevésbé kívánatos megoldás: az operációs rendszer mindig előzetesen futó szálat előkészít, függetlenül annak prioritási szintjétől, így a processzorhoz való hozzáférés a rendszer minden szálához biztosított.

Mivel a .NET -et futtató Windows összes verziójának kvantálási sémái minden szálhoz minimális időszeletet tartalmaznak. Másrészt, ha a .NET keretrendszert valaha más rendszerekhez igazítják, ez változhat.

Ha a Start sor hívása előtt a következő sort vesszük be programunkba, akkor még a legalacsonyabb prioritású szálak is megkapják a CPU idő töredékét:

bThread.Priority = ThreadPriority.Highest

Rizs. 10.3. A legmagasabb prioritású szál általában gyorsabban indul

Rizs. 10.4. A processzor alacsonyabb prioritású szálakhoz is rendelkezésre áll

A parancs a maximális prioritást rendeli hozzá az új szálhoz, és csökkenti a fő szál prioritását. Ábra. 10.3 látható, hogy az új szál gyorsabban kezd működni, mint korábban, de ahogy az ábra. 10.4, a fő szál is vezérlést kaplustaság (bár nagyon rövid ideig és csak az áramlás hosszadalmas kivonással végzett munkája után). Ha futtatja a programot a számítógépén, akkor hasonló eredményeket kap, mint az ábrán látható. 10.3 és 10.4, de a rendszereink közötti különbségek miatt nem lesz pontos egyezés.

A ThreadPrlority felsorolt ​​típus öt prioritási szint értékeit tartalmazza:

SzálPrioritás. Legmagasabb

TémaPriority.AbourNormal

ThreadPrlority.Normal

ThreadPriority.BelowNormal

SzálPrioritás.Leg legalacsonyabb

Csatlakozási módszer

Néha egy programszálat szüneteltetni kell, amíg egy másik szál be nem fejeződik. Tegyük fel, hogy szüneteltetni szeretné az 1. szálat, amíg a 2. szál befejezi a számítást. Ezért az 1 -es folyamról a csatlakozás metódust a 2. folyamra hívják. Más szóval a parancs

szál 2. Csatlakozz ()

felfüggeszti az aktuális szálat, és várja a 2. szál befejeződését zárt állapot.

Ha a Csatlakozás módszerrel csatlakozik az 1. adatfolyamhoz a 2. adatfolyamhoz, akkor az operációs rendszer automatikusan elindítja az 1. adatfolyamot a 2. folyam után. Ne feledje, hogy az indítási folyamat nem determinisztikus: lehetetlen pontosan megmondani, hogy a 2. szál befejezése után mennyi idő múlva kezd működni az 1. szál. Van egy másik változata is, amely logikai értéket ad vissza:

szál 2. Csatlakozás (egész szám)

Ez a módszer vagy megvárja, amíg a 2. szál befejeződik, vagy feloldja az 1. szál letiltását a megadott időintervallum letelte után, ami miatt az operációs rendszer ütemezője újra lefoglalja a CPU -időt a szálhoz. A metódus igaz értéket ad vissza, ha a 2. szál a megadott időkorlát lejárta előtt befejeződik, és hamis, ha nem.

Ne feledje az alapvető szabályt: függetlenül attól, hogy a 2. szál befejeződött vagy időtúllépés történt, nincs szabályozása az 1. szál aktiválásának időpontja felett.

Szálnevek, CurrentThread és ThreadState

A Thread.CurrentThread tulajdonság egy hivatkozást ad vissza az éppen végrehajtott szálobjektumra.

Bár van egy csodálatos szál ablak a többszálas alkalmazások hibakereséséhez a VB .NET -ben, amelyet az alábbiakban ismertetünk, a parancs gyakran segített nekünk

MsgBox (Thread.CurrentThread.Name)

Gyakran kiderült, hogy a kódot egy teljesen más szálban hajtják végre, ahonnan azt kellett volna végrehajtani.

Emlékezzünk vissza, hogy a "programfolyamatok nem determinisztikus ütemezése" kifejezés nagyon egyszerű dolgot jelent: a programozónak gyakorlatilag nincs eszköze az ütemező munkájának befolyásolására. Emiatt a programok gyakran használják a ThreadState tulajdonságot, amely információt szolgáltat a szál aktuális állapotáról.

Patakok ablak

A Visual Studio .NET szálainak ablaka felbecsülhetetlen értékű a többszálas programok hibakeresésében. A Debug> Windows almenü paranccsal aktiválja megszakítás módban. Tegyük fel, hogy nevet adott a bThread szálnak a következő paranccsal:

bThread.Name = "Szál kivonása"

A folyamok ablakának hozzávetőleges nézete a program megszakítása után a Ctrl + Break billentyűkombinációval (vagy más módon) az ábrán látható. 10.5.

Rizs. 10.5. Patakok ablak

Az első oszlopban lévő nyíl jelzi a Thread.CurrentThread tulajdonság által visszaadott aktív szálat. Az ID oszlop numerikus szál -azonosítókat tartalmaz. A következő oszlop felsorolja a folyamneveket (ha hozzá van rendelve). A Location oszlop jelzi a futtatandó eljárást (például a Konzol osztály WriteLine eljárása a 10.5. Ábrán). A többi oszlop a prioritással és felfüggesztett szálakkal kapcsolatos információkat tartalmaz (lásd a következő részt).

A szál ablak (nem az operációs rendszer!) Lehetővé teszi a program szálainak vezérlését a helyi menük segítségével. Például leállíthatja az aktuális szálat, ha a jobb gombbal rákattint a megfelelő sorra, és a Freeze parancsot választja (később a leállított szál folytatható). A hibakeresés során gyakran használnak leállító szálakat, hogy megakadályozzák a hibás szálak zavarását az alkalmazásban. Ezenkívül a folyamok ablak lehetővé teszi egy másik (nem leállított) adatfolyam aktiválását; Ehhez kattintson a jobb gombbal a kívánt sorra, és válassza a helyi menüből a Váltás szálra parancsot (vagy egyszerűen kattintson duplán a szálsorra). Amint az alábbiakban bemutatjuk, ez nagyon hasznos a lehetséges patthelyzetek diagnosztizálásában.

Folyamat felfüggesztése

Az ideiglenesen fel nem használt folyamok a Slеer módszerrel passzív állapotba vihetők át. A passzív adatfolyamot is blokkoltnak kell tekinteni. Természetesen, ha egy szálat passzív állapotba helyeznek, a többi szálnak több processzor -erőforrása lesz. A Slеer módszer standard szintaxisa a következő: Thread.Sleep (interval_in_milliseconds)

Az alvó hívás eredményeként az aktív szál passzívvá válik legalább egy meghatározott számú ezredmásodpercig (azonban a megadott intervallum letelte után történő aktiválás nem garantált). Kérjük, vegye figyelembe: a metódus meghívásakor egy adott szálra való hivatkozás nem kerül átadásra - az alvó módszert csak az aktív szál hívja meg.

Az alvás egy másik verziója arra készteti az aktuális szálat, hogy feladja a többi CPU -időt:

Téma. Alvás (0)

A következő lehetőség korlátlan ideig passzív állapotba hozza az aktuális szálat (az aktiválás csak akkor történik, ha megszakítja a hívást):

Thread.Slеer (Timeout.Infinite)

Mivel a passzív szálakat (akár korlátlan időtúllépéssel is) meg lehet szakítani a megszakítási módszerrel, ami kivételes esetben egy ThreadlnterruptExcept kezdeményezéséhez vezet, a Slayer hívás mindig egy Try-Catch blokkba van zárva, mint a következő részletben:

Próbáld ki

Téma. Alvás (200)

"A szál passzív állapota megszakadt

Catch e mint kivétel

"Más kivételek

Vége Próba

Minden .NET program egy programszálon fut, ezért az Alvás módszert a programok felfüggesztésére is használják (ha a Threadipg névteret nem importálja a program, akkor a Threading.Thread. Sleep teljes körű minősített nevet kell használni).

A program szálainak leállítása vagy megszakítása

A szál automatikusan leáll a ThreadStart delegált létrehozásakor megadott módszerrel, de néha szükség van a módszer (és így a szál) leállítására bizonyos tényezők előfordulása esetén. Ilyen esetekben a folyamok általában ellenőrzik feltételes változó,állapotától függőendöntés születik a patakból való vészkijáratról. Általában egy Do-while ciklus szerepel az eljárásban:

Sub ThreadedMethod ()

„A programnak biztosítania kell a felméréshez szükséges eszközöket

"feltételes változó.

"Például egy feltételes változó tulajdonságként stílusosítható

Do while conditionVariable = False And MoreWorkToDo

"A fő kód

Loop End Sub

A feltételes változó lekérdezése eltart egy ideig. Csak akkor használjon állandó lekérdezést ciklusállapotban, ha várja, hogy a szál idő előtt leálljon.

Ha a feltételváltozót egy adott helyen kell ellenőrizni, használja a Ha-Akkor parancsot az Exit Sub-nal együtt egy végtelen ciklusban.

A feltételes változóhoz való hozzáférést szinkronizálni kell, hogy a más szálakból származó expozíció ne zavarja annak normál használatát. Ezzel a fontos témával a "Hibaelhárítás: szinkronizálás" szakasz foglalkozik.

Sajnos a passzív (vagy más módon blokkolt) szálak kódja nem kerül végrehajtásra, így a feltételes változó lekérdezése opció nem megfelelő számukra. Ebben az esetben hívja meg a megszakítás metódust azon objektumváltozón, amely hivatkozást tartalmaz a kívánt szálra.

A megszakítási módszer csak várakozás, alvás vagy csatlakozás állapotú szálakon hívható meg. Ha a megszakítást hívja egy szálhoz, amely a felsorolt ​​állapotok egyikében van, akkor egy idő után a szál újra elkezd működni, és a végrehajtási környezet kivételesen kezdeményezi a szál ThreadlnterruptExcepcióját. Ez akkor is előfordul, ha a szálat határozatlan ideig passzívvá tették a Thread.Sleepdimeout meghívásával. Végtelen). Azt mondjuk "egy idő után", mert a szálütemezés nem determinisztikus. A ThreadlnterruptExcept kivételesen elkapja a Catch szakasz, amely tartalmazza a várakozási állapotból való kilépési kódot. A Catch szakasznak azonban nem kell megszakítania a szálat egy megszakítási hívásnál - a szál úgy kezeli a kivételt, ahogy jónak látja.

A .NET -ben a megszakítási módszer még a blokkolt szálak esetében is meghívható. Ebben az esetben a szál a legközelebbi blokkolásnál megszakad.

Fonalak felfüggesztése és megölése

A Threading névtér más módszereket is tartalmaz, amelyek megszakítják a normál szálazást:

  • Felfüggesztés;
  • Elvetél.

Nehéz megmondani, hogy a .NET miért támogatta ezeket a módszereket - a Suspend és az Abort hívása valószínűleg instabillá teszi a programot. Egyik módszer sem teszi lehetővé az adatfolyam normál deinitializálását. Ezenkívül a Felfüggesztés vagy Megszakítás hívásakor lehetetlen megjósolni, hogy a szál milyen állapotban hagyja az objektumokat a felfüggesztés vagy megszakítás után.

Az Abort hívása ThreadAbortException -t dob. Hogy megértsük, miért nem szabad kezelni ezt a furcsa kivételt a programokban, itt egy részlet a .NET SDK dokumentációjából:

„... Ha az Abort meghívásával egy szál megsemmisül, a futásidő ThreadAbortException -t dob. Ez egy különleges kivétel, amelyet a program nem érhet el. Amikor ezt a kivételt eldobja, a futási idő lefuttatja az összes végül blokkot, mielőtt befejezi a szálat. Mivel a Végső blokkokban bármilyen művelet végrehajtható, hívja a Join gombot, hogy megbizonyosodjon arról, hogy a folyam megsemmisül. "

Erkölcsi: A megszakítás és a felfüggesztés nem ajánlott (és ha továbbra sem sikerül a felfüggesztés nélkül, folytassa a felfüggesztett szálat a Folytatás módszerrel). A szálat biztonságosan le lehet állítani csak szinkronizált állapotváltozó lekérdezésével vagy a fent tárgyalt megszakítási módszer meghívásával.

Háttér szálak (démonok)

A háttérben futó egyes szálak automatikusan leállnak, amikor más programkomponensek leállnak. Különösen a szemétszedő fut az egyik hátsó szálban. A háttérszálakat általában adatok fogadására hozzák létre, de ez csak akkor történik meg, ha más szálak olyan kódot futtatnak, amely képes feldolgozni a fogadott adatokat. Szintaxis: adatfolyam neve. IsBackGround = Igaz

Ha csak háttér szálak maradnak az alkalmazásban, az alkalmazás automatikusan leáll.

Komolyabb példa: adatok kinyerése HTML kódból

Csak akkor javasoljuk a folyamok használatát, ha a program funkcionalitása egyértelműen több műveletre oszlik. Jó példa erre a 9. fejezetben található HTML -kitermelő program. Az osztályunk két dolgot tesz: adatokat gyűjt az Amazon -ról és feldolgozza. Ez tökéletes példa arra a helyzetre, amikor a többszálas programozás valóban megfelelő. Osztályokat hozunk létre különböző könyvekhez, majd elemezzük az adatokat különböző adatfolyamokban. Új szál létrehozása minden könyvhöz növeli a program hatékonyságát, mivel amíg az egyik szál adatokat fogad (amihez várni kell az Amazon szerverén), egy másik szál foglalt lesz a már fogadott adatok feldolgozásával.

Ennek a programnak a többszálas változata hatékonyabban működik, mint az egyszálú változat, csak több processzoros számítógépen, vagy ha további adatok fogadása hatékonyan kombinálható az elemzésükkel.

Amint fentebb említettük, csak olyan eljárások futtathatók szálakban, amelyeknek nincs paramétere, ezért kisebb módosításokat kell végrehajtania a programon. Az alábbiakban bemutatjuk az alapvető eljárást, amelyet átírtunk a paraméterek kizárásához:

Nyilvános al FindRank ()

m_Rank = ScrapeAmazon ()

Console.WriteLine ("rang" & m_Name & "Is" & GetRank)

End Sub

Mivel nem fogjuk tudni használni a kombinált mezőt információk tárolására és visszakeresésére (többszálú programok írását grafikus interfésszel tárgyaljuk e fejezet utolsó szakaszában), a program négy könyv adatait tárolja tömbben, meghatározása így kezdődik:

A könyv elsötétítése (3.1) A könyv karakterláncaként (0.0) = "1893115992"

theBook (0.l) = "VB .NET programozása" "Stb.

Négy adatfolyam jön létre ugyanabban a ciklusban, amelyben az AmazonRanker objektumok jönnek létre:

I = 0 és 3 között

Próbáld ki

theRanker = Új AmazonRanker (TheBook (i.0). TheBookd.1))

aThreadStart = Új ThreadStar (AddressRofer.FindRan ()

aThread = Új szál (aThreadStart)

aThread.Name = a könyv (i.l)

aThread.Start () Fogás e kivételként

Console.WriteLine (e.Message)

Vége Próba

Következő

Az alábbiakban a program teljes szövege olvasható:

Opció Szigorú az importálási rendszerben.IO Imports System.Net

Importálási rendszer

Modul modul

Sub Main ()

A könyv tompítása (3.1) karakterláncként

a könyv (0.0) = "1893115992"

theBook (0.l) = "VB .NET programozása"

a könyv (l.0) = "1893115291"

theBook (l.l) = "Adatbázis -programozás VB .NET"

a könyv (2,0) = "1893115623"

theBook (2.1) = "Programozó bevezetője a C #-hoz."

a könyv (3.0) = "1893115593"

theBook (3.1) = "Tömörítse a .Net platformot"

Dim i As Integer

Dim theRanker As = AmazonRanker

Dim aThreadStart mint Threading.ThreadStart

Dim aThread mint Threading.Thread

I = 0 és 3 között

Próbáld ki

theRanker = Új AmazonRankerttheBook (i.0). a könyv (i.1))

aThreadStart = New ThreadStart (AddressRofer. FindRank)

aThread = Új szál (aThreadStart)

aThread.Name = a könyv (i.l)

aThread.Start ()

Catch e mint kivétel

Console.WriteLlnete.Message)

Vége Próbálkozzon tovább

Console.ReadLine ()

End Sub

Vége modul

Nyilvános osztályú AmazonRanker

Privát m_URL karakterláncként

Privát m_Rank mint egész

Privát m_név karakterláncként

Nyilvános alújság (ByVal ISBN mint karakterlánc. ByVal theName as karakterlánc)

m_URL = "http://www.amazon.com/exec/obidos/ASIN/" & ISBN

m_Name = theName End Sub

Nyilvános al FindRank () m_Rank = ScrapeAmazon ()

Console.Writeline ("a" & m_Name & "rang"

& GetRank) End Sub

Nyilvános, csak olvasható tulajdon GetRank () karakterláncként

Ha m_Rank<>0 Akkor

Vissza CStr (m_Rank) Más

"Problémák

Vége Ha

Vége Get

Tulajdon vége

Nyilvános, csak olvasható tulajdon GetName () karakterláncként

Vissza az m_Name

Vége Get

Tulajdon vége

Privát funkció ScrapeAmazon () egész számként

Az url elsötétítése új uriként (m_URL)

A kérés elhalványítása webes kérésként

theRequest = WebRequest.Create (theURL)

A válasz elhalványítása WebResponse -ként

theResponse = theRequest.GetResponse

Dim aReader új StreamReaderként (theResponse.GetResponseStream ())

Az adat halványítása karakterláncként

theData = aReader.ReadToEnd

Visszatérési elemzés (theData)

E fogás kivételként

Console.WriteLine (E.Message)

Console.WriteLine (E.StackTrace)

Konzol. ReadLine ()

Vége Próba Vége Funkció

Privát funkcióelemzés (ByVal theData as String) mint egész

Halvány hely As.Integer Location = theData.IndexOf (" Amazon.com

Értékesítési rang:") _

+ "Amazon.com értékesítési rangsor:".Hossz

Halvány hőmérséklet mint karakterlánc

Do Before theData.Substring (Location.l) = "<" temp = temp

& theData.Substring (Location.l) Location + = 1 Loop

Vissza Clnt (temp)

Vége funkció

Vége az osztálynak

A .NET és I / O névterekben általában többszálas műveleteket használnak, ezért a .NET -keretrendszer speciális aszinkron módszereket biztosít számukra. További információ az aszinkron módszerek többszálas programok írása során történő használatáról a HTTPWebRequest osztály BeginGetResponse és EndGetResponse metódusaiban található.

Fő veszély (általános adatok)

Eddig a menetek egyetlen biztonságos felhasználási esetét vették figyelembe - folyamaink nem változtattak az általános adatokon. Ha engedélyezi az általános adatok megváltoztatását, a lehetséges hibák exponenciálisan szaporodni kezdenek, és sokkal nehezebb lesz megszabadulni tőlük a program számára. Másrészről, ha megtiltja a megosztott adatok különböző szálakon történő módosítását, akkor a többszálú .NET programozás alig fog eltérni a VB6 korlátozott lehetőségeitől.

Szeretnénk felhívni a figyelmét egy kis programra, amely bemutatja a felmerülő problémákat anélkül, hogy felesleges részletekbe menne. Ez a program egy házat szimulál, termosztáttal minden szobában. Ha a hőmérséklet 5 Fahrenheit fok vagy annál több (kb. 2,77 Celsius fok) alacsonyabb, mint a célhőmérséklet, elrendeljük, hogy a fűtési rendszer növelje a hőmérsékletet 5 fokkal; ellenkező esetben a hőmérséklet csak 1 fokkal emelkedik. Ha az aktuális hőmérséklet nagyobb vagy egyenlő a beállítottnál, akkor nem történik változás. A hőmérsékletszabályozás minden helyiségben 200 milliszekundumos késleltetéssel, külön áramlással történik. A fő munkát az alábbi részlettel végezzük:

Ha mHouse.HouseTemp< mHouse.MAX_TEMP = 5 Then Try

Téma. Alvás (200)

Catch nyakkendő ThreadlnterruptException

„A passzív várakozás megszakadt

Catch e mint kivétel

"Más végső próba kivételek

mHouse.HouseTemp + - 5 "stb.

Az alábbiakban a program teljes forráskódja található. Az eredmény az ábrán látható. 10.6: A ház hőmérséklete elérte a 105 Fahrenheit fokot (40,5 Celsius fok)!

1 Opció Szigorúan be

2 Importálási rendszer

3 Modul modul

4 Sub Main ()

5 Dim myHouse új házként (l0)

6 Konzol. ReadLine ()

7 End Sub

8 Vége modul

9 Nyilvános osztályház

10 Nyilvános Const MAX_TEMP mint egész = 75

11 Privát mCurTemp mint egész = 55

12 Privát mRooms () mint szoba

13 Nyilvános alújság (ByVal numOfRooms as Integer)

14 ReDim mRooms (számOfRooms = 1)

15 Dim i As Integer

16 Dim aThreadStart mint Threading.ThreadStart

17 Dim aThread mint Thread

18 For i = 0 to numOfRooms -1

19 Próbálja ki

20 mRooms (i) = NewRoom (Me, mCurTemp, CStr (i) & "throom")

21 aThreadStart - Új ThreadStart (AddressOf _

mRooms (i). CheckTempInRoom)

22 aThread = Új szál (aThreadStart)

23 aTéma.Start ()

24 E fogás kivételként

25 Console.WriteLine (E.StackTrace)

26 Vége Próba

27 Következő

28 End Sub

29 Public Property HouseTemp () mint egész

harminc . Kap

31 Vissza az mCurTemp

32 End Get

33 Set (ByVal Value as Integer)

34 mCurTemp = 35. érték Végkészlet

36 Végtulajdon

37 Vége az osztálynak

38 Nyilvános osztályterem

39 Privát mCurTemp mint egész

40 Privát mName, mint karakterlánc

41 Privát mHouse As House

42 Nyilvános Sub New (ByVal theHouse As House,

ByVal temp as Integer, ByVal roomName as String)

43 mHouse = theHouse

44 mCurTemp = hőmérséklet

45 mName = szobaName

46 End Sub

47 Nyilvános al CheckTempInRoom ()

48 Hőmérséklet változtatása ()

49 End Sub

50 Privát alváltozás Hőmérséklet ()

51 Próbálja ki

52 Ha mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then

53 Szál. Alvás (200)

54 mHouse.HouseTemp + - 5

55 Console.WriteLine ("Am in" & Me.mName & _

56 ".A jelenlegi hőmérséklet" & mHouse.HouseTemp)

57. Elself mHouse.HouseTemp< mHouse.MAX_TEMP Then

58 Szál. Alvás (200)

59 mHouse.HouseTemp + = 1

60 Console.WriteLine ("Am in" & Me.mName & _

61 ".A jelenlegi hőmérséklet" & mHouse.HouseTemp)

62 Különben

63 Console.WriteLine ("Am in" & Me.mName & _

64 ".A jelenlegi hőmérséklet" & mHouse.HouseTemp)

65 "Ne csinálj semmit, a hőmérséklet normális

66 Vége Ha

67 Catch tae ThreadlnterruptException

68 "A passzív várakozás megszakadt

69 Fogás e Kivétel

70 "Egyéb kivételek

71 Vége Próbálja ki

72 End Sub

73 Vége osztály

Rizs. 10.6. Többszálas kérdések

A Sub Main eljárás (4-7. Sor) "házat" hoz létre tíz "szobával". A House osztály maximális hőmérsékletét 75 Fahrenheit -fokban (körülbelül 24 Celsius fok) állítja be. A 13-28. Sorok egy meglehetősen összetett házépítőt határoznak meg. A program megértésének kulcsa a 18-27. A 20. sor létrehoz egy másik szobaobjektumot, és a házobjektumra való hivatkozást továbbítja a konstruktornak, hogy a helyiségobjektum szükség esetén hivatkozzon rá. A 21-23. Sorok tíz folyamot indítanak, hogy beállítsák a helyiség hőmérsékletét. A szobaosztály a 38-73. Ház coxpa referenciaa Room osztály konstruktor mHouse változójában tárolódik (43. sor). A hőmérséklet ellenőrzésének és beállításának kódja (50-66. Sor) egyszerűnek és természetesnek tűnik, de amint hamarosan látni fogja, ez a benyomás csalóka! Ne feledje, hogy ez a kód egy Try-Catch blokkba van csomagolva, mert a program az alvó módszert használja.

Aligha vállalná valaki, hogy 105 Fahrenheit (40,5-24 Celsius fok) hőmérsékleten él. Mi történt? A probléma a következő sorhoz kapcsolódik:

Ha mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then

És a következő történik: először a hőmérsékletet az 1. áramlás ellenőrzi. Látja, hogy a hőmérséklet túl alacsony, és 5 fokkal megemeli. Sajnos, mielőtt a hőmérséklet emelkedne, az 1. folyam megszakad, és a vezérlés átkerül a 2. folyamba. A 2. adatfolyam ugyanazt a változót ellenőrzi, még nem változottáramlás 1. Így a 2. áramlás is készül a hőmérséklet 5 fokos emelésére, de nincs ideje erre, és várakozó állapotba is kerül. A folyamat addig folytatódik, amíg az 1. folyam aktiválódik, és folytatódik a következő parancs - a hőmérséklet 5 fokkal történő növelése. A növekedés megismétlődik, ha mind a 10 adatfolyam aktiválódik, és a ház lakói rosszul fogják érezni magukat.

A probléma megoldása: szinkronizálás

Az előző programban olyan helyzet áll elő, amikor a program kimenete a szálak végrehajtási sorrendjétől függ. Ahhoz, hogy megszabaduljon tőle, meg kell győződnie arról, hogy a parancsok tetszenek

Ha mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then...

az aktív szál teljesen feldolgozza, mielőtt megszakadna. Ezt a tulajdonságot ún atomi szégyen - minden szálnak megszakítás nélkül, atomegységként kell végrehajtania egy kódblokkot. Az atomblokkba egyesített parancsok csoportját a szálütemező nem tudja megszakítani, amíg be nem fejeződik. Bármely többszálas programozási nyelvnek megvan a maga módja az atomosság biztosítására. A VB .NET -ben a SyncLock parancs használatának legegyszerűbb módja, ha behívjuk egy objektumváltozót. Végezzen apró változtatásokat a ChangeTemperature eljárásban az előző példából, és a program jól fog működni:

Privát alváltozás Hőmérséklet () SyncLock (mHouse)

Próbáld ki

Ha mHouse.HouseTemp< mHouse.MAXJTEMP -5 Then

Téma. Alvás (200)

mHouse.HouseTemp + = 5

Console.WriteLine ("Am in" & Me.mName & _

".A jelenlegi hőmérséklet" & mHouse.HouseTemp)

Önmagában

mHouse.HouseTemp< mHouse. MAX_TEMP Then

Menet. Alvó (200) mHouse.HouseTemp + = 1

Console.WriteLine ("Am in" & Me.mName & _ ".A jelenlegi hőmérséklet" & mHouse.HomeTemp) Más

Console.WriteLineC "Am in" & Me.mName & _ ".A jelenlegi hőmérséklet" & mHouse.HouseTemp)

"Ne csináljon semmit, a hőmérséklet normális

Vége, ha a fogási nyakkendő szálként megszakad Kivétel

"A passzív várakozást a Catch e Exception kivételével megszakította

"Más kivételek

Vége Próba

A SyncLock leállítása

End Sub

A SyncLock blokk kód atomosan hajtódik végre. A hozzáférés az összes többi szálról le lesz zárva, amíg az első szál ki nem oldja a zárat a SyncLock befejezése paranccsal. Ha a szinkronizált mondatban egy szál passzív várakozási állapotba kerül, a zár mindaddig megmarad, amíg a szál megszakad vagy folytatódik.

A SyncLock parancs helyes használata biztonságban tartja a programszálat. Sajnos a SyncLock túlzott használata negatív hatással van a teljesítményre. A kód szinkronizálása egy többszálas programban többszörösére csökkenti a munka sebességét. Csak a legszükségesebb kódot szinkronizálja, és a lehető leghamarabb engedje fel a zárat.

Az alapgyűjtési osztályok nem biztonságosak a többszálas alkalmazásokban, de a .NET-keretrendszer a legtöbb gyűjteményosztály szálbiztos verzióját tartalmazza. Ezekben az osztályokban a potenciálisan veszélyes módszerek kódját a SyncLock blokkok tartalmazzák. A gyűjteményosztályok szálbiztos verzióit kell használni többszálú programokban, bárhol is sérül az adatok integritása.

Feltétlenül meg kell említeni, hogy a feltételes változók könnyen megvalósíthatók a SyncLock paranccsal. Ehhez csak szinkronizálnia kell az írást a közös logikai tulajdonsággal, amely olvasható és írható, ahogyan az a következő töredékben történik:

Nyilvános osztályfeltételVáltozó

Privát megosztott szekrény objektumként = Új objektum ()

Privát, megosztott mOK, mint Boolean Shared

Tulajdonság TheConditionVariable () mint Boolean

Kap

Vissza a mOK

Vége Get

Set (ByVal Value as Boolean) SyncLock (szekrény)

mOK = Érték

A SyncLock leállítása

Beállítás készlet

Tulajdon vége

Vége az osztálynak

SyncLock parancs és monitor osztály

A SyncLock parancs használata néhány finomságot tartalmaz, amelyeket a fenti egyszerű példák nem mutattak be. Tehát a szinkronizációs objektum megválasztása nagyon fontos szerepet játszik. Próbálja meg futtatni az előző programot a SyncLock (Me) paranccsal a SyncLock (mHouse) helyett. A hőmérséklet ismét a küszöb fölé emelkedik!

Ne feledje, hogy a SyncLock parancs használatával szinkronizál tárgy, paraméterként adja át, nem a kódrészlet által. A SyncLock paraméter ajtóként szolgál a szinkronizált töredék eléréséhez más szálakból. A SyncLock (Me) parancs valójában több különböző "ajtót" nyit meg, pontosan ezt akarta elkerülni a szinkronizálással. Erkölcs:

A többszálú alkalmazások megosztott adatainak védelme érdekében a SyncLock parancsnak egyszerre csak egy objektumot kell szinkronizálnia.

Mivel a szinkronizálás egy adott objektumhoz kapcsolódik, bizonyos helyzetekben előfordulhat, hogy véletlenül zárol más töredékeket. Tegyük fel, hogy két szinkronizált metódusa van, az első és a második, és mindkét módszer szinkronizálva van a bigLock objektumon. Amikor az 1. szál először lép be a metódusba, és rögzíti a bigLock -ot, egyetlen szál sem léphet be a második módszerbe, mert a hozzáférés már az 1. szálra korlátozódik!

A SyncLock parancs funkcionalitását úgy tekinthetjük, mint a Monitor osztály funkcionalitásának részhalmazát. A Monitor osztály nagyon testreszabható, és nem triviális szinkronizálási feladatok megoldására használható. A SyncLock parancs a Moni tor osztály Enter és Exi metódusainak közelítő analógja:

Próbáld ki

Monitor. Írja be (theObject) Végül

Monitor.Eit (theObject)

Vége Próba

Néhány szabványos művelethez (változó növelése / csökkentése, két változó tartalmának cseréje) a .NET -keretrendszer biztosítja az Interlocked osztályt, amelynek módszerei ezeket a műveleteket atomi szinten végzik. Az Interlocked osztály használatával ezek a műveletek sokkal gyorsabbak, mint a SyncLock parancs használata.

Reteszelés

A szinkronizálás során a zár objektumokra van állítva, nem szálakra, tehát használatakor különböző blokkolni kívánt objektumokat különböző kódrészletek a programokban néha egészen nem triviális hibák fordulnak elő. Sajnos sok esetben a szinkronizálás egyetlen objektumon egyszerűen elfogadhatatlan, mivel túl gyakran blokkolja a szálakat.

Fontolja meg a helyzetet összekapcsolódó(holtpont) a legegyszerűbb formában. Képzeljünk el két programozót az ebédlőasztalnál. Sajnos csak egy késük és egy villájuk van kettőre. Ha feltételezzük, hogy kés és villa is szükséges az étkezéshez, két helyzet lehetséges:

  • Az egyik programozónak sikerül megragadnia egy kést és villát, és enni kezd. Ha jóllakott, félreteszi a vacsorát, majd egy másik programozó elviheti.
  • Az egyik programozó elveszi a kést, a másik a villát. Egyikük sem kezdheti el az evést, ha a másik nem adja fel a készülékét.

Egy többszálas programban ezt a helyzetet ún kölcsönös blokkolás. A két módszer szinkronizálása különböző objektumokon történik. Az A szál rögzíti az 1 objektumot, és belép az objektum által védett programrészbe. Sajnos ahhoz, hogy működjön, hozzá kell férnie egy másik szinkronizálási zár által védett kódhoz, más szinkronizációs objektummal. De mielőtt ideje lenne egy másik objektum által szinkronizált töredék beírására, a B folyam belép, és rögzíti ezt az objektumot. Most az A szál nem léphet be a második töredékbe, a B szál nem léphet be az első töredékbe, és mindkét szál végtelen várakozásra van ítélve. Egy szál sem futhat tovább, mert a szükséges objektum soha nem lesz felszabadítva.

A holtpont diagnosztizálását bonyolítja, hogy viszonylag ritka esetekben fordulhatnak elő. Minden attól függ, hogy az ütemező milyen sorrendben osztja ki nekik a CPU időt. Lehetséges, hogy a legtöbb esetben a szinkronizációs objektumok nem holtpont sorrendben kerülnek rögzítésre.

Az alábbiakban az imént leírt patthelyzet megvalósítását mutatjuk be. A legalapvetőbb pontok rövid tárgyalása után megmutatjuk, hogyan lehet azonosítani a holtpontot a szálablakban:

1 Opció Szigorúan be

2 Importálási rendszer

3 Modul modul

4 Sub Main ()

5 Dim Tom új programozóként ("Tom")

6 Dim Bob új programozóként ("Bob")

7 Dim aThreadStart mint új ThreadStart (Tom.Eat címe)

8 Dim aThread mint új szál (aThreadStart)

9 aThread.Name = "Tom"

10 Dim bThreadStart as New ThreadStarttAddressOf Bob.Eat)

11 Dim bThread mint új szál (bThreadStart)

12 bThread.Name = "Bob"

13 aTéma.Start ()

14 bTéma.Start ()

15 End Sub

16 Vége modul

17 Nyilvános osztályú villa

18 Private Shared mForkAvaiTable As Boolean = Igaz

19 Private Shared mOwner as String = "Senki"

20 privát, csak olvasható tulajdon tulajdonosa az Utensil () karakterláncnak

21 Kap

22 Visszatérés mOwner

23 End Get

24 Végtulajdon

25 Nyilvános Sub GrabForktByVal a mint programozó)

26 Console.Writel_ine (Thread.CurrentThread.Name & _

"megpróbálta megragadni a villát.")

27 Console.WriteLine (Me.OwnsUtensil & "rendelkezik villával."). ...

28 Monitor.Enter (Me) "SyncLock (aFork)"

29 Ha az mForkAvailable Akkor

30 a.HasFork = Igaz

31 mTulajdonos = a.Nyame

32 mForkAvailable = Hamis

33 Console.WriteLine (a.MyName & "most van a villa. Vár")

34 Próbálja ki

Thread.Sleep (100) Catch e Exception Console.WriteLine (e.StackTrace)

Vége Próba

35 Vége Ha

36 Monitor. Kilépés (én)

A SyncLock leállítása

37 End Sub

38 Vége osztály

39 Nyilvános osztályú kés

40 Privát megosztott mKnifeAvailable Boolean = True

41 Private Shared mOwner as String = "Senki"

42 Privát, csak olvasható tulajdon tulajdonosa az Utensi1 () karakterláncnak

43 Kap

44 Visszatérés mOwner

45 End Get

46 Végtulajdon

47 Nyilvános Sub GrabKnifetByVal a mint programozó)

48 Console.WriteLine (Thread.CurrentThread.Name & _

"megpróbálja megragadni a kést.")

49 Console.WriteLine (Me.OwnsUtensil & "rendelkezik a késsel.")

50 Monitor.Enter (Me) "SyncLock (aKnife)"

51 Ha az mKnifeAvailable Akkor

52 mKnifeAvailable = Hamis

53 a.HasKnife = Igaz

54 mTulajdonos = a.Nyame

55 Console.WriteLine (a.MyName & "most van a kés. Vár")

56 Próbálja ki

Téma. Alvás (100)

Catch e mint kivétel

Console.WriteLine (e.StackTrace)

Vége Próba

57 Vége Ha

58 Monitor. Kilépés (én)

59 End Sub

60 Vége osztály

61 Nyilvános osztályú programozó

62 Privát mName, mint karakterlánc

63 Private Shared mFork As Fork

64 Privát, megosztott mKnife, mint kés

65 Privát mHasKnife mint Boolean

66 Privát mHasFork mint Boolean

67 Megosztott új ()

68 mFork = Új villa ()

69 mKés = Új kés ()

70 End Sub

71 Nyilvános alújság (ByVal theName as String)

72 mName = a név

73 End Sub

74 Nyilvános, csak olvasható tulajdonság MyName () mint karakterlánc

75 Kap

76 Return mName

77 End Get

78 Végtulajdon

79 Köztulajdon HasKnife () mint Boolean

80 Kap

81 Vissza a mHasKnife

82 Vége

83 Set (ByVal Value as Boolean)

84 mHasKnife = Érték

85 Végkészlet

86 Végtulajdon

87 Köztulajdon HasFork () mint Boolean

88 Kap

89 Vissza a mHasFork

90 End Get

91 Set (ByVal Value as Boolean)

92 mHasFork = Érték

93 Végkészlet

94 Végtulajdon

95 Nyilvános alétkezés ()

96 Do Do Me Me.HasKnife And Me.HasFork

97 Console.Writeline (Thread.CurrentThread.Name & "benne van a szálban.")

98 Ha Rnd ()< 0.5 Then

99 mFork.GrabFork (én)

100 Különben

101 mKés.GrabKnife (én)

102 Vége Ha

103 Hurok

104 MsgBox (Me.Neame & "ehetek!")

105 mKés = Új kés ()

106 mFork = Új villa ()

107 End Sub

108 Vége az osztálynak

A Main fő eljárás (4-16. Sor) két példányt hoz létre a Programozó osztályból, majd két szálat indít a Programozó osztály kritikus Eat metódusának végrehajtásához (95-108. Sor), az alábbiakban leírtak szerint. A Main eljárás beállítja a szálak nevét és beállítja azokat; valószínűleg minden, ami történik, érthető és megjegyzés nélkül.

A Fork osztály kódja érdekesebbnek tűnik (17-38. Sor) (hasonló Késosztály van megadva a 39-60. Sorokban). A 18. és a 19. sor meghatározza a közös mezők értékeit, amelyek alapján megtudhatja, hogy a dugó jelenleg elérhető -e, és ha nem, akkor ki használja. A OwnOtensi1 ReadOnly tulajdonság (20-24. Sor) az információ legegyszerűbb továbbítására szolgál. A Fork osztály központi eleme a GrabFork „fogd a villát” módszer, amelyet a 25-27.

  1. A 26. és 27. sor egyszerűen nyomtatja a hibakeresési információkat a konzolra. A módszer fő kódjában (28-36. Sor) a villához való hozzáférést objektum szinkronizáljaöv Én. Mivel programunk csak egy villát használ, a Me sync biztosítja, hogy két szál ne tudja egyszerre megragadni. A Slee "p parancs (a 34. sorban kezdődő blokkban) a villát / kést megragadás és az evés kezdete közötti késleltetést szimulálja. Vegye figyelembe, hogy a Sleep parancs nem oldja fel az objektumokat, és csak gyorsítja a patthelyzeteket!
    A legérdekesebb azonban a Programozó osztály kódja (61-108. Sor). A 67-70. Sorok általános konstruktőrt határoznak meg annak biztosítására, hogy csak egy villa és kés legyen a programban. A tulajdonságkód (74-94. Sor) egyszerű, és nem igényel megjegyzést. A legfontosabb dolog az Eat módszerben történik, amelyet két külön szál hajt végre. A folyamat ciklusban folytatódik, amíg néhány patak el nem fogja a villát a késsel együtt. A 98-102 sorokban az objektum véletlenszerűen megragadja a villát / kést az Rnd hívás segítségével, ami a patthelyzetet okozza. A következő történik:
    A Thoth objektum Eat metódusát végrehajtó szál meghívásra kerül és belép a ciklusba. Fogja a kést, és várakozó állapotba kerül.
  2. A Bob Eat módszerét végrehajtó szál meghívásra kerül és belép a ciklusba. Nem tudja megragadni a kést, de megragadja a villát, és várakozó állapotba kerül.
  3. A Thoth objektum Eat metódusát végrehajtó szál meghívásra kerül és belép a ciklusba. Megpróbálja megragadni a villát, de Bob már megragadta a villát; a szál várakozó állapotba kerül.
  4. A Bob Eat módszerét végrehajtó szál meghívásra kerül és belép a ciklusba. Megpróbálja megragadni a kést, de a kést már elfogta Thoth tárgya; a szál várakozó állapotba kerül.

Mindez a végtelenségig folytatódik - tipikus holtpontra nézünk (próbáld meg futtatni a programot, és látni fogod, hogy senki sem képes így enni).
Azt is ellenőrizheti, hogy nem történt -e holtpont a szálak ablakában. Futtassa a programot, és szakítsa meg a Ctrl + Break billentyűkkel. Vegye fel a Me változót a nézetablakba, és nyissa meg a folyamok ablakot. Az eredmény olyan, mint az ábrán látható. 10.7. Az ábrán látható, hogy Bob cérnája kést ragadott, de villája nincs. Kattintson a jobb egérgombbal a Tot vonal Threads ablakában, és válassza a Switch to Thread parancsot a helyi menüből. A nézetablak azt mutatja, hogy a Thoth -pataknak van villája, de nincs kés. Persze ez nem százszázalékos bizonyíték, de az ilyen viselkedés legalábbis azt gyanítja, hogy valami nincs rendben.
Ha az egyik objektum által történő szinkronizálás lehetősége (például a programban a ház hőmérsékletének növelésével) nem lehetséges, a kölcsönös zárolás megakadályozása érdekében számozhatja a szinkronizációs objektumokat, és mindig állandó sorrendben rögzítheti őket. Folytassuk az étkezési programozó analógiáját: ha a szál mindig először a kést, majd a villát veszi el, akkor nem lesz probléma a holtpont -zárással. Az első patak, amely megragadja a kést, képes lesz normálisan enni. A programfolyamok nyelvére lefordítva ez azt jelenti, hogy a 2 -es objektum rögzítése csak akkor lehetséges, ha először rögzíti az 1 -es objektumot.

Rizs. 10.7. A holtpont elemzése a szál ablakban

Ezért ha eltávolítjuk a hívást az Rnd -hez a 98 -as vonalon, és lecseréljük a kódrészletre

mFork.GrabFork (én)

mKnife.GrabKnife (én)

a holtpont eltűnik!

Együttműködés az adatok létrehozásakor

Többszálú alkalmazásokban gyakran előfordul az a helyzet, hogy a szálak nem csak a megosztott adatokkal dolgoznak, hanem meg is várják, amíg megjelennek (vagyis az 1. szálnak létre kell hoznia az adatokat, mielőtt a 2. szál használni tudná). Mivel az adatok megosztottak, az adatokhoz való hozzáférést szinkronizálni kell. Szükséges továbbá eszközöket biztosítani a várakozó szálak értesítésére a kész adatok megjelenéséről.

Ezt a helyzetet általában ún a beszállító / fogyasztó problémája. A szál még nem létező adatokhoz próbál hozzáférni, ezért át kell adnia az irányítást egy másik szálnak, amely létrehozza a szükséges adatokat. A problémát a következő kóddal oldják meg:

  • Az 1. szál (fogyasztó) felébred, megad egy szinkronizált módszert, megkeresi az adatokat, nem találja, és várakozási állapotba kerül. Előzetesenfizikailag el kell távolítania a blokkolást, hogy ne zavarja az ellátó szál munkáját.
  • A 2. szál (szolgáltató) az 1. szál által felszabadított szinkronizált metódust adja meg, teremt adatot az 1. adatfolyamhoz, és valahogy értesíti az 1. adatfolyamot az adatok jelenlétéről. Ezután feloldja a zárat, hogy az 1 szál fel tudja dolgozni az új adatokat.

Ne próbálja megoldani ezt a problémát úgy, hogy folyamatosan meghívja az 1. szálat, és ellenőrzi a feltételváltozó állapotát, amelynek értékét> a 2. szál állítja be. Ez a döntés komolyan befolyásolja a program teljesítményét, mivel a legtöbb esetben az 1. szál ok nélkül hivatkozni; és a 2. szál olyan gyakran vár, hogy elfogy az idő az adatok létrehozásához.

A szolgáltatói / fogyasztói kapcsolatok nagyon gyakoriak, ezért speciális primitíveket hoznak létre ilyen helyzetekre a többszálas programozási osztálykönyvtárakban. A NET-ben ezeket a primitíveket Wait és Pulse-PulseAl 1-nek hívják, és a Monitor osztály részét képezik. A 10.8. Ábra szemlélteti a programozásra váró helyzetet. A program három szálsorot szervez: várakozási sort, blokkolási sort és végrehajtási sort. A szálütemező nem oszt ki CPU -időt a várakozási sorban lévő szálakra. Ahhoz, hogy egy szál időt rendelhessen hozzá, a végrehajtási sorba kell lépnie. Ennek eredményeként az alkalmazás munkája sokkal hatékonyabban szerveződik, mint egy feltételes változó szokásos lekérdezésével.

Az álkódban az adatfogyasztói idióma a következőképpen van megfogalmazva:

"Belépés a következő típusú szinkronizált blokkba

Miközben nincs adat

Menjen a várakozási sorba

Hurok

Ha van adat, dolgozza fel.

Hagyja el a szinkronizált blokkot

Közvetlenül a Várakozás parancs végrehajtása után a szál felfüggesztésre kerül, a zár kiold, és a szál belép a várakozási sorba. A zár kioldásakor a végrehajtási sorban lévő szál futhat. Idővel egy vagy több blokkolt szál létrehozza a várakozási sorban lévő szál működéséhez szükséges adatokat. Mivel az adatok érvényesítését ciklusban hajtják végre, az adathasználatra való áttérés (a ciklus után) csak akkor következik be, ha a feldolgozásra kész adatok rendelkezésre állnak.

Az álkódban az adatszolgáltató idióma így néz ki:

"Szinkronizált nézetblokk megadása

Bár adatokra NEM szükség

Menjen a várakozási sorba

Egyéb adatok előállítása

Ha az adatok készen állnak, hívja a Pulse-PulseAll-t.

hogy egy vagy több szálat a blokkolási sorból a végrehajtási sorba vigyen. Hagyja el a szinkronizált blokkot (és térjen vissza a futási sorba)

Tegyük fel, hogy a programunk szimulálja a családot, amelynek egyik szülője pénzt keres, és egy gyermeket, aki ezt a pénzt költi. Amikor a pénznek végekiderül, hogy a gyermeknek meg kell várnia egy új összeg megérkezését. Ennek a modellnek a szoftveres megvalósítása így néz ki:

1 Opció Szigorúan be

2 Importálási rendszer

3 Modul modul

4 Sub Main ()

5 A család elhalványítása új családként ()

6 theFamily.StartltsLife ()

7 End Sub

8 Vége a fjodule -nek

9

10 Nyilvános osztályú család

11 Privát mMoney mint egész

12 Privát mWeek Integer = 1

13 Nyilvános alindulási élet ()

14 Dim aThreadStart ThreadStarUAddressOf Me.Produce)

15 Dim bThreadStart as New ThreadStarUAddressOf Me. Consume)

16 Dim aThread mint új szál (aThreadStart)

17 Dim bThread mint új szál (bThreadStart)

18 aThread.Name = "Gyártás"

19 aTéma.Start ()

20 bThread.Name = "Fogyasztás"

21 bSzál. Rajt ()

22 End Sub

23 Köztulajdon A Hét () mint egész

24 Kap

25 Visszahét

26 End Get

27 Set (ByVal Value as Integer)

28 hét - Érték

29 Végkészlet

30 Végtulajdon

31 Köztulajdon OurMoney () mint egész

32 Kap

33 Visszatérés mMoney

34 End Get

35 Set (ByVal Value as Integer)

36 mPénz = Érték

37 Végkészlet

38 Végtulajdon

39 Nyilvános alprodukció ()

40 szál. Alvás (500)

41 Tedd

42 Monitor. Írja be (én)

43 Tedd meg én. Pénzünk> 0

44 Monitor. Várj (én)

45 Hurok

46 Me. Pénzünk = 1000

47 Monitor.PulseAll (én)

48 Monitor. Kilépés (én)

49 Hurok

50 End Sub

51 Nyilvános részfogyasztás ()

52 MsgBox ("Fogyasztási szálban vagyok")

53 Tedd

54 Monitor. Írja be (én)

55 Csinálj én. Pénzünk = 0

56 Monitor. Várj (én)

57 Hurok

58 Console.WriteLine ("Kedves szülő, most költöttem el mindenedet" & _

pénz a héten "és TheWeek)

59 Hét + = 1

60 Ha a hét = 21 * 52 akkor a System.Environment.Exit (0)

61 Me. Pénzünk = 0

62 Monitor.PulseAll (én)

63 Monitor. Kilépés (én)

64 Hurok

65 End Sub

66 Vége osztály

A StartltsLife módszer (13-22. Sor) előkészíti a Produce és Consume folyamok elindítását. A legfontosabb a Produce (39-50. Sorok) és a Consume (51-65. Sor) folyamokban történik. A Sub Produce eljárás ellenőrzi a pénz rendelkezésre állását, és ha van pénz, akkor a várakozási sorba kerül. Ellenkező esetben a szülő pénzt generál (46. sor), és értesíti a várakozási sorban lévő objektumokat a helyzet változásáról. Ne feledje, hogy a Pulse-Pulse All hívása csak akkor lép érvénybe, ha a Monitor.Exit paranccsal feloldják a zárat. Ezzel szemben a Sub Consume eljárás ellenőrzi a pénz rendelkezésre állását, és ha nincs pénz, értesíti erről a várakozó szülőt. A 60. sor 21 feltételes év után egyszerűen leállítja a programot; hívja a rendszert. A Environment.Exit (0) az End parancs .NET analógja (a End parancs szintén támogatott, de ellentétben a System. Environment. Exit paranccsal, nem ad vissza kilépési kódot az operációs rendszernek).

A várakozási sorba helyezett szálakat a program más részeinek fel kell szabadítaniuk. Éppen ezért inkább a PulseAll -t használjuk a Pulse -on keresztül. Mivel előre nem lehet tudni, hogy melyik szál aktiválódik az 1. impulzus hívásakor, és viszonylag kis számú szál van a sorban, ezért ugyanúgy hívhatja a PulseAll -t.

Többszálú grafikai programokban

A GUI -alkalmazások többszálúságáról szóló vitánk egy példával kezdődik, amely elmagyarázza, mire szolgál a GUI -alkalmazásokban a többszálúság. Hozzon létre egy űrlapot két gombbal: Start (btnStart) és Mégse (btnCancel), az ábra szerint. 10.9. A Start gombra kattintva egy osztály jön létre, amely 10 millió karakterből álló véletlenszerű karakterláncot tartalmaz, és egy módszert az "E" betű előfordulásának számítására a hosszú karakterláncban. Vegye figyelembe a StringBuilder osztály használatát a hosszú karakterláncok hatékonyabb létrehozásához.

1. lépés

Az 1. szál észreveszi, hogy nincs rá adat. Várakozást hív, feloldja a zárat, és a várakozási sorba lép.



2. lépés

A zár kioldásakor a 2. vagy 3. szál elhagyja a blokksort, és belép egy szinkronizált mondatba, és megszerzi a zárat

3. lépés

Tegyük fel, hogy a 3. szál belép egy szinkronizált blokkba, adatokat hoz létre és hívja a Pulse-Pulse All-t.

Közvetlenül azután, hogy kilép a blokkból és feloldja a zárat, az 1 szál a végrehajtási sorba kerül. Ha a 3. szál hívja Pluse -t, akkor csak egy lép be a végrehajtási sorbaszál, amikor a Pluse All meghívásra kerül, minden szál a végrehajtási sorba kerül.



Rizs. 10.8. Szolgáltatói / fogyasztói probléma

Rizs. 10.9. Többszálú egyszerű GUI alkalmazásban

Importálás System.Text

Nyilvános osztályú véletlenszerű karakterek

Privát m_Data StringBuilderként

Privát mjength, m_count As Integer

Nyilvános al új (ByVal n mint egész)

m_hossz = n -1

m_Data = Új StringBuilder (m_length) MakeString ()

End Sub

Privát al MakeString ()

Dim i As Integer

Dim myRnd mint új véletlen ()

I = 0 -tól m_hosszig

"Hozzon létre egy véletlenszerű számot 65 és 90 között,

"konvertálja nagybetűsre

"és csatolja a StringBuilder objektumhoz

m_Data.Append (Chr (myRnd.Next (65.90)))

Következő

End Sub

Nyilvános alindítási szám ()

GetEes ()

End Sub

Private Sub GetEes ()

Dim i As Integer

I = 0 -tól m_hosszig

Ha m_Data.Chars (i) = CChar ("E") Akkor

m_szám + = 1

Vége Ha Tovább

m_CountDone = Igaz

End Sub

Nyilvános, csak olvasható

Tulajdon GetCount () egész számként

Ha nem (m_CountDone) Akkor

M_szám visszaadása

Vége Ha

End Get End Property

Nyilvános, csak olvasható

A tulajdonság IsDone () a logikai érték szerint

Visszatérés

m_CountDone

Vége Get

Tulajdon vége

Vége az osztálynak

Az űrlap két gombjához nagyon egyszerű kód kapcsolódik. A btn-Start_Click eljárás példányosítja a fenti RandomCharacters osztályt, amely 10 millió karakterből álló karakterláncot foglal magában:

Private Sub btnStart_Click (ByVal feladó mint System.Object.

ByVal e As System.EventArgs) A btnSTart.Click elemet kezeli

Dim RC új véletlenszerű karakterekként (10000000)

RC.StartCount ()

MsgBox ("Az esek száma" és RC.GetCount)

End Sub

A Mégse gomb megjeleníti az üzenetmezőt:

Private Sub btnCancel_Click (ByVal feladó mint System.Object._

ByVal e As System.EventArgs) A btnCancel.Click kezelése

MsgBox ("Megszakadt a számolás!")

End Sub

A program futtatásakor és a Start gomb megnyomásakor kiderül, hogy a Mégse gomb nem reagál a felhasználói bevitelre, mert a folyamatos hurok megakadályozza, hogy a gomb kezelje a kapott eseményt. Ez a modern programokban elfogadhatatlan!

Két lehetséges megoldás létezik. A korábbi VB verziókból jól ismert első lehetőség eltekint a többszálasítástól: a DoEvents hívás szerepel a ciklusban. A NET -ben ez a parancs így néz ki:

Application.DoEvents ()

Példánkban ez biztosan nem kívánatos - ki akar lelassítani egy programot tízmillió DoEvents hívással! Ha ehelyett a hurkot külön szálhoz rendeli, akkor az operációs rendszer vált a szálak között, és a Mégse gomb működőképes marad. Az alábbiakban bemutatjuk a megvalósítást külön szállal. Annak érdekében, hogy egyértelműen megmutassuk, hogy a Mégse gomb működik, amikor rákattintunk, egyszerűen leállítjuk a programot.

Következő lépés: Számlálás gomb megjelenítése

Tegyük fel, hogy úgy döntött, hogy megmutatja kreatív fantáziáját, és megadja a formának az ábrán látható megjelenést. 10.9. Kérjük, vegye figyelembe: a Számlálás megjelenítése gomb még nem érhető el.

Rizs. 10.10. Zárolt gomb űrlap

Várhatóan egy külön szál fogja elvégezni a számlálást és feloldani a nem elérhető gombot. Ezt természetesen meg lehet tenni; ráadásul elég gyakran felmerül ilyen feladat. Sajnos nem fog tudni a legkézenfekvőbb módon cselekedni - kapcsolja össze a másodlagos szálat a GUI szállal úgy, hogy megtartja a konstruktor ShowCount gombjára mutató linket, vagy akár egy szokásos delegáltot. Más szavakkal, soha ne használja az alábbi opciót (alap téves a sorok félkövér).

Nyilvános osztályú véletlenszerű karakterek

Privát m_0ata StringBuilderként

Privát m_CountDone Booleanként

Privát mjength. m_count As Integer

Privát m_Button Mint Windows.Forms.Button

Nyilvános al új (ByVa1 n mint egész, _

ByVal b Mint Windows.Forms.Button)

m_hossz = n - 1

m_Data = Új StringBuilder (mJength)

m_Button = b MakeString ()

End Sub

Privát al MakeString ()

Dim I mint egész

Dim myRnd mint új véletlen ()

I = 0 m_hosszig

m_Data.Append (Chr (myRnd.Next (65.90)))

Következő

End Sub

Nyilvános alindítási szám ()

GetEes ()

End Sub

Private Sub GetEes ()

Dim I mint egész

I = 0 -ig

Ha m_Data.Chars (I) = CChar ("E") Akkor

m_szám + = 1

Vége Ha Tovább

m_CountDone = Igaz

m_Button.Enabled = Igaz

End Sub

Nyilvános, csak olvasható

Tulajdon GetCount () mint egész

Kap

Ha nem (m_CountDone) Akkor

Dobj új kivételt ("A számolás még nem készült el") Más

M_szám visszaadása

Vége Ha

Vége Get

Tulajdon vége

Nyilvános, csak olvasható tulajdon IsDone () logikai értékként

Kap

Visszatérés m_CountDone

Vége Get

Tulajdon vége

Vége az osztálynak

Valószínű, hogy ez a kód bizonyos esetekben működni fog. Mindazonáltal:

  • A másodlagos szál kölcsönhatása a GUI -t létrehozó szállal nem szervezhető nyilvánvaló eszközök.
  • Soha ne módosítsa a grafikus programok elemeit más programfolyamokból. Minden változtatás csak abban a szálban történjen meg, amely létrehozta a GUI -t.

Ha megszegi ezeket a szabályokat, mi garanciát vállalunk hogy finom, finom hibák fordulnak elő többszálas grafikus programjaiban.

Ezenkívül nem sikerül megszervezni az objektumok kölcsönhatását események segítségével. A 06-esemény dolgozója ugyanazon a szálon fut, amelyet a RaiseEvent-nek hívtak, így az események nem segítenek.

A józan ész mégis azt diktálja, hogy a grafikus alkalmazásoknak rendelkezniük kell egy eszközzel egy másik szál elemeinek módosítására. A NET-keretrendszerben van egy szálbiztos módszer a GUI-alkalmazások metódusainak meghívására egy másik szálról. A Method Invoker egy speciális típusa a System.Windows névtérből. Űrlapok. A következő részlet a GetEes módszer új verzióját mutatja (a megváltozott sorok vastagon szedve):

Private Sub GetEes ()

Dim I mint egész

I = 0 m_hosszig

Ha m_Data.Chars (I) = CChar ("E") Akkor

m_szám + = 1

Vége Ha Tovább

m_CountDone = Igaz próbálkozás

Dim mylnvoker új módszerként (AddressOf UpDateButton)

myInvoker.Invoke () Catch e ThreadlnterruptException

"Kudarc

Vége Próba

End Sub

Public Sub UpDateButton ()

m_Button.Enabled = Igaz

End Sub

A gombok közötti szálközi hívások nem közvetlenül, hanem a Method Invokeren keresztül történnek. A .NET -keretrendszer garantálja, hogy ez az opció szálbiztos.

Miért van annyi probléma a többszálas programozással?

Most, hogy van némi ismerete a többszálúságról és az ezzel kapcsolatos lehetséges problémákról, úgy döntöttünk, hogy helyénvaló megválaszolni a fejezet végén található alcímben feltett kérdést.

Ennek egyik oka, hogy a többszálú folyamat nem lineáris folyamat, és megszoktuk a lineáris programozási modellt. Eleinte nehéz hozzászokni ahhoz a gondolathoz, hogy a program végrehajtása véletlenszerűen megszakítható, és a vezérlés más kódra kerül.

Van azonban egy másik, sokkal alaposabb ok is: manapság a programozók túl ritkán programoznak assemblerben, vagy legalábbis nézik a fordító szétszerelt kimenetét. Ellenkező esetben sokkal könnyebb lenne megszokniuk azt a gondolatot, hogy több tucat összeszerelési utasítás felelhet meg egy magas szintű nyelv (például VB .NET) egyik parancsának. A szál megszakítható ezen utasítások bármelyike ​​után, tehát egy magas szintű parancs közepén.

De ez még nem minden: a modern fordítók optimalizálják a program teljesítményét, és a számítógép hardvere zavarhatja a memóriakezelést. Ennek eredményeként a fordító vagy a hardver az Ön tudta nélkül megváltoztathatja a program forráskódjában megadott parancsok sorrendjét [ Sok fordító optimalizálja a tömbök ciklikus másolását, például i = 0 -tól n -ig: b (i) = a (i): ncxt. A fordító (vagy akár egy speciális memóriakezelő) egyszerűen létrehozhat egy tömböt, majd kitöltheti azt egyetlen másolási művelettel, ahelyett, hogy sokszor másolná az egyes elemeket!].

Remélhetőleg ezek a magyarázatok segítenek abban, hogy jobban megértsék, miért okoz a többszálas programozás ennyi problémát - vagy legalábbis kevésbé lep meg a többszálas programok furcsa viselkedése miatt!

Példa egy egyszerű, többszálas alkalmazás felépítésére.

A Delphi -ben többszálú alkalmazások építésével kapcsolatos sok kérdés okán született.

Ennek a példának az a célja, hogy bemutassa, hogyan kell megfelelően létrehozni egy többszálú alkalmazást, a hosszú távú munka eltávolításával egy külön szálban. És hogyan lehet egy ilyen alkalmazásban biztosítani a fő szál kölcsönhatását a dolgozóval az űrlapról (vizuális összetevők) az adatfolyamba történő adattovábbításhoz és fordítva.

A példa nem állítja teljesnek, csak a szálak közötti interakció legegyszerűbb módjait mutatja be. Lehetővé teszi a felhasználó számára, hogy "gyorsan elvakítsa" (ki tudja, mennyire utálom) egy megfelelően működő többszálas alkalmazást.
Minden benne van részletesen kommentálva (véleményem szerint), de ha kérdésed van, tedd fel.
De még egyszer figyelmeztetlek: A streamek nem könnyűek... Ha fogalma sincs, hogyan működik ez az egész, akkor óriási a veszélye annak, hogy gyakran minden jól fog működni, és néha a program több mint furcsán viselkedik. A helytelenül írt, többszálas program viselkedése nagymértékben függ számos olyan tényezőtől, amelyek néha nem reprodukálhatók a hibakeresés során.

Tehát egy példa. A kényelem érdekében mind a kódot elhelyeztem, mind pedig csatoltam az archívumot a modul- és űrlapkóddal

egység ExThreadForm;

felhasznál
Windows, üzenetek, SysUtils, változatok, osztályok, grafika, vezérlők, űrlapok,
Párbeszédablakok, StdCtrls;

// állandók, amelyeket az adatfolyamból egy űrlapba történő adatátvitelkor használunk
// ablaküzenetek küldése
const
WM_USER_SendMessageMetod = WM_USER + 10;
WM_USER_PostMessageMetod = WM_USER + 11;

típus
// a szál osztályának leírása, a tThread leszármazottja
tMyThread = osztály (tThread)
magán
SyncDataN: Egész szám;
SyncDataS: Karakterlánc;
eljárás SyncMetod1;
védett
eljárás Végrehajtás; felülbírálás;
nyilvános
Param1: Karakterlánc;
Param2: Egész;
Param3: Boolean;
Megállt: Boolean;
UtolsóVéletlen: Integer;
IterationNo: Egész;
Eredménylista: tStringList;

Konstruktor létrehozása (aParam1: String);
destruktor Destroy; felülbírálás;
vége;

// az űrlap osztály leírása a folyam segítségével
TForm1 = osztály (TForm)
Címke1: TLabel;
Memo1: TMemo;
btnStart: TButton;
btnStop: TButton;
Szerkesztés1: TEdit;
Szerkesztés2: TEdit;
CheckBox1: TCheckBox;
Címke2: TLabel;
3. címke: TLabel;
4. címke: TLabel;
eljárás btnStartClick (Feladó: TObject);
eljárás btnStopClick (Feladó: TObject);
magán
(Magánbevallások)
MyThread: tMyThread;
eljárás EventMyThreadOnTerminate (Feladó: tObject);
eljárás EventOnSendMessageMetod (var Msg: TMessage); üzenet WM_USER_SendMessageMetod;
eljárás EventOnPostMessageMetod (var Msg: TMessage); üzenet WM_USER_PostMessageMetod;

Nyilvános
(Nyilvános nyilatkozatok)
vége;

var
1. forma: TForm1;

{
Leállítva - Az űrlapról adatfolyamba történő adatátvitelt mutatja.
További szinkronizálás nem szükséges, mivel egyszerű
egyszavas típus, és csak egy szál írja.
}

eljárás TForm1.btnStartClick (Feladó: TObject);
kezdődik
Randomize (); // a véletlenszerűség biztosítása a sorozatban Random () segítségével - semmi köze a folyamathoz

// Hozzon létre egy példányt a folyamobjektumból, adja át annak egy bemeneti paramétert
{
FIGYELEM!
A stream konstruktor úgy van írva, hogy létrejön a stream
felfüggesztett, mert lehetővé teszi:
1. Irányítsa az indítás pillanatát. Ez szinte mindig kényelmesebb, mert
lehetővé teszi a patak beállítását még az indulás előtt, adja át a bemenetet
paramétereket stb.
2. Mert a létrehozott objektumra mutató hivatkozás az űrlapmezőbe kerül mentésre, majd
a szál önpusztítása után (lásd alább), amely a szál futásakor
bármikor előfordulhat, ez a link érvénytelenné válik.
}
MyThread: = tMyThread.Create (Form1.Edit1.Text);

// Mivel azonban a szál felfüggesztve jött létre, minden hiba esetén
// inicializálása során (az indítás előtt) magunknak kell megsemmisítenünk
// amire használjuk, próbálja meg a / kivéve blokkot
próbáld ki

// Szál -lezáró kezelő hozzárendelése, amelyben megkapjuk
// a folyam munkájának eredményeit, és "felülírja" a rá mutató linket
MyThread.OnTerminate: = EventMyThreadOnTerminate;

// Mivel az eredményeket az OnTerminate -ben gyűjtik, azaz az önpusztítás előtt
// a patak, akkor levesszük a megsemmisítés gondjait
MyThread.FreeOnTerminate: = Igaz;

// Példa a bemeneti paraméterek átvitelére a folyamobjektum mezőin, a ponton
// példányosítani, ha még nem fut.
// Személy szerint ezt inkább a felülbírált paraméterein keresztül teszem
// konstruktor (tMyThread.Create)
MyThread.Param2: = StrToInt (Form1.Edit2.Text);

MyThread.Stopped: = Hamis; // egyfajta paraméter is, de változik
// szál futási ideje
kivéve
// mivel a szál még nem kezdődött el és nem lesz képes önpusztítani, ezért "kézzel" megsemmisítjük
FreeAndNil (MyThread);
// majd hagyja, hogy a kivétel a szokásos módon legyen kezelve
emel;
vége;

// Mivel a szálobjektum létrehozása és konfigurálása sikeres volt, ideje elkezdeni
MyThread.Resume;

ShowMessage ("A stream elindult");
vége;

eljárás TForm1.btnStopClick (Feladó: TObject);
kezdődik
// Ha a szálpéldány még mindig létezik, kérje meg, hogy állítsa le
// És pontosan "kérdezz". Elvileg "kényszeríthetünk" is, de lesz
// rendkívül vészhelyzeti lehetőség, amely mindezek világos megértését igényli
// streaming konyha. Ezért itt nem veszik figyelembe.
ha hozzárendelve (MyThread) akkor
MyThread.Stopped: = Igaz
más
ShowMessage ("A szál nem fut!");
vége;

eljárás TForm1.EventOnSendMessageMetod (var Msg: TMessage);
kezdődik
// módszer szinkron üzenet feldolgozására
// a WParamban a tMyThread objektum címe, LParamban a szál LastRandom aktuális értéke
tMyThread (Msg.WParam) segítségével kezdődik
Form1.Label3.Caption: = Format ("% d% d% d",);
vége;
vége;

eljárás TForm1.EventOnPostMessageMetod (var Msg: TMessage);
kezdődik
// módszer aszinkron üzenetek kezelésére
// WParamban az IterationNo aktuális értéke, LParamban a LastRandom adatfolyam aktuális értéke
Form1.Label4.Caption: = Format ("% d% d",);
vége;

eljárás TForm1.EventMyThreadOnTerminate (Feladó: tObject);
kezdődik
// FONTOS!
// Az OnTerminate esemény kezelésének módszerét mindig a main összefüggésében hívják meg
// szál - ezt a tThread megvalósítás garantálja. Ezért benne szabadon
// bármely objektum tulajdonságait és módszereit használja

// Minden esetre győződjön meg arról, hogy az objektumpéldány még mindig létezik
ha nincs hozzárendelve (MyThread), akkor lépjen ki; // ha nincs ott, akkor nincs mit tenni

// a szálobjektum példányának szálának munkájának eredményeinek lekérése
Form1.Memo1.Lines.Add (Format ("A folyam% d eredménnyel zárult",));
Form1.Memo1.Lines.AddStrings ((Feladó mint tMyThread) .ResultList);

// A folyamobjektum -példányra vonatkozó hivatkozás megsemmisítése.
// Mivel a szálunk önpusztító (FreeOnTerminate: = True)
// majd az OnTerminate kezelő befejezése után a stream objektum példány lesz
// megsemmisítve (ingyenes), és minden hivatkozás érvénytelenné válik.
// Nehogy véletlenül belefusson egy ilyen linkbe, írja felül a MyThread -et
// Még egyszer megjegyzem - nem pusztítjuk el az objektumot, hanem csak felülírjuk a linket. Egy tárgy
// tönkreteszi magát!
MyThread: = Nil;
vége;

konstruktor tMyThread.Create (aParam1: String);
kezdődik
// Hozzon létre egy példányt a SUSPENDED folyamból (lásd a megjegyzést példányosításkor)
örökölt Create (True);

// Belső objektumok létrehozása (ha szükséges)
Eredménylista: = tStringList.Create;

// Kezdeti adatok lekérése.

// A paraméteren átvitt bemeneti adatok másolása
Param1: = aParam1;

// Példa a bemeneti adatok fogadására a VCL -összetevőktől a stream objektum konstruktorában
// Ez ebben az esetben elfogadható, mivel a konstruktor meghívása a kontextusban történik
// fő szál. Ezért a VCL komponensek itt érhetők el.
// De ezt nem szeretem, mert szerintem rossz, ha a szál tud valamit
// valamilyen formáról ott. De mit nem tud tenni a demonstráció érdekében.
Param3: = Form1.CheckBox1.Checked;
vége;

destructor tMyThread.Destroy;
kezdődik
// belső objektumok megsemmisítése
FreeAndNil (Eredménylista);
// tönkreteszi az alap tThread
örökölt;
vége;

eljárás tMyThread.Execute;
var
t: bíboros;
s: karakterlánc;
kezdődik
IterationNo: = 0; // az eredmények számlálója (ciklusszám)

// Példámban a szál törzse egy hurok, amely véget ér
// vagy egy külső "befejezési kéréssel" a Stopped változó paraméteren keresztül,
// vagy csak 5 hurok elvégzésével
// Számomra kellemesebb ezt "örök" hurkon keresztül írni.

Míg az Igaz kezdődik

Inc (IterationNo); // következő ciklus száma

UtolsóVéletlen: = Véletlen (1000); // kulcsszám - a paraméterek adatfolyamból az űrlapba való átvitelének bemutatására

T: = Véletlenszerű (5) +1; // az az idő, amelyre elalszunk, ha nem fejezzük be

// Hülye munka (a bemeneti paramétertől függően)
ha nem akkor a Param3
Inc (Param2)
más
Dec (Param2);

// Közbenső eredményt alkot
s: = Formátum ("% s% 5d% s% d% d",
);

// Közbenső eredmény hozzáadása az eredmények listájához
ResultList.Add (s);

//// Példák egy közbülső eredmény űrlapra továbbítására

//// Szinkronizált módszer átadása - a klasszikus módszer
//// Hátrányok:
//// - a szinkronizálandó módszer általában a stream osztály metódusa (a hozzáféréshez
//// a folyamobjektum mezeihez), de az űrlapmezők eléréséhez feltétlenül szükséges
//// "tud" róla és annak mezőiről (tárgyairól), ami általában nem túl jó
//// a program szervezésének nézőpontja.
//// - az aktuális szál felfüggesztésre kerül, amíg a végrehajtás befejeződik
//// szinkronizált módszer.

//// Előnyök:
//// - szabványos és sokoldalú
//// - szinkronizált módszerben használhatja
//// a stream objektum összes mezője.
// először, ha szükséges, el kell mentenie az átvitt adatokat
// az objektum objektum speciális mezői.
SyncDataN: = IterationNo;
SyncDataS: = "Szinkronizálás" + s;
//, majd szinkronizált metódushívást biztosít
Szinkronizálás (SyncMetod1);

//// Küldés szinkron üzenetküldéssel (SendMessage)
//// ebben az esetben az adatok mind az üzenetparamétereken (LastRandom) keresztül továbbíthatók,
//// és az objektum mezőin keresztül, átadva a példány címét az üzenetparaméterben
//// a stream objektum - Integer (Self).
//// Hátrányok:
//// - a szálnak ismernie kell az űrlapablak fogantyúját
//// - a Szinkronizáláshoz hasonlóan az aktuális szál felfüggesztésre kerül
//// az üzenetfeldolgozás befejezése a fő szállal
//// - jelentős mennyiségű CPU -időt igényel minden híváshoz
//// (szál váltáshoz), ezért nagyon gyakori hívás nem kívánatos
//// Előnyök:
//// - a Szinkronizáláshoz hasonlóan az üzenetek feldolgozásakor is használhatja
//// a folyamobjektum összes mezője (ha természetesen a címe meg lett adva)


//// indítsa el a szálat.
SendMessage (Form1.Handle, WM_USER_SendMessageMetod, Integer (Self), LastRandom);

//// Átvitel aszinkron üzenetküldéssel (PostMessage)
//// Mivel ebben az esetben, mire az üzenetet a fő szál megkapja,
//// előfordulhat, hogy a küldő adatfolyam már befejeződött, átadva a példány címét
//// a stream objektum érvénytelen!
//// Hátrányok:
//// - a szálnak ismernie kell az űrlapablak fogantyúját;
//// - az aszinkronitás miatt az adatátvitel csak paramétereken keresztül lehetséges
//// üzenetek, ami jelentősen megnehezíti a méretes adatok továbbítását
//// több mint két gépi szó. Kényelmes használni egész szám áthaladásához stb.
//// Előnyök:
//// - az előző módszerekkel ellentétben az aktuális szál NEM
//// szünetel, és azonnal folytatja a végrehajtást
//// - ellentétben a szinkronizált hívással, üzenetkezelővel
//// egy űrlapmódszer, amelynek ismernie kell a stream objektumot,
//// vagy semmit sem tud a folyamról, ha csak az adatokat továbbítják
//// az üzenetparamétereken keresztül. Vagyis a szál nem tudhat semmit az űrlapról.
//// általában - csak a fogantyúja, amely korábban paraméterként adható át
//// indítsa el a szálat.
PostMessage (Űrlap1.Handle, WM_USER_PostMessageMetod, IterationNo, LastRandom);

//// Ellenőrizze a lehetséges befejezést

// A befejezés ellenőrzése paraméter szerint
ha megállt, akkor szünet;

// Néha ellenőrizze a befejezést
ha IterationNo> = 10 akkor Break;

Alvás (t * 1000); // elalszik t másodpercre
vége;
vége;

tMyThread.SyncMetod1 eljárás;
kezdődik
// ezt a módszert a Synchronize metóduson keresztül hívják meg.
// Vagyis annak ellenére, hogy a tMyThread szál metódusa,
// az alkalmazás fő szálának összefüggésében fut.
// Ezért bármit megtehet, nos, vagy majdnem mindent :)
// De ne feledd, itt nem érdemes sokáig "piszkálni"

// Az átadott paramétereket kivonhatjuk a speciális mezőkből, ahol megvannak
// hívás előtt mentve.
Form1.Label1.Caption: = SyncDataS;

// vagy a stream objektum más mezőiből, például annak aktuális állapotát tükrözve
Form1.Label2.Caption: = Format ("% d% d",);
vége;

Általában a példát a következő érvelésem előzte meg a témában ...

Először:
A Delphi többszálas programozásának LEGFONTOSABB szabálya:
Egy nem fő szál összefüggésében nem férhet hozzá az űrlapok tulajdonságaihoz és módszereihez, sőt, a tWinControlból "növekvő" összes összetevőhöz.

Ez azt jelenti (némileg leegyszerűsítve), hogy sem a TThread -től örökölt Execute metódusban, sem az Execute -ból meghívott egyéb metódusokban / eljárásokban / függvényekben, ez tiltott közvetlenül elérheti a vizuális összetevők tulajdonságait és módszereit.

Hogyan kell helyesen csinálni.
Nincsenek egységes receptek. Pontosabban annyi és különböző lehetőség van, hogy a konkrét esettől függően választani kell. Ezért hivatkoznak a cikkre. Miután elolvasta és megértette, a programozó képes lesz megérteni, és hogyan kell a legjobban megtenni egy adott esetben.

Röviden az ujjain:

Leggyakrabban a többszálú alkalmazás akkor válik, ha valamilyen hosszú távú munkát kell végezni, vagy amikor egyszerre több olyan dolgot is meg lehet tenni, amelyek nem terhelik meg erősen a processzort.

Az első esetben a főszálon belüli munka végrehajtása a felhasználói felület "lelassulásához" vezet - a munka közben az üzenethurok nem hajtódik végre. Ennek eredményeként a program nem reagál a felhasználói műveletekre, és az űrlap nem rajzolódik ki, például a felhasználó mozgatása után.

A második esetben, amikor a munka aktív cserét tartalmaz a külvilággal, akkor az erőltetett "állásidő" alatt. Amíg várakozik az adatok fogadására / küldésére, párhuzamosan mást is végezhet, például ismét küldhet / fogadhat adatokat.

Vannak más esetek is, de ritkábban. Azonban nem számít. Most nem erről van szó.

Nos, hogyan írják le mindezt. Természetesen figyelembe kell venni egy bizonyos leggyakoribb esetet, kissé általánosítva. Így.

A külön szálban végzett munkának általában négy entitása van (nem tudom, hogyan kell pontosabban nevezni):
1. Kezdeti adatok
2. Valójában maga a munka (függhet a kezdeti adatoktól)
3. Közbenső adatok (például információk a munka végrehajtásának jelenlegi állapotáról)
4. Kimeneti adatok (eredmény)

Leggyakrabban a vizuális összetevőket használják az adatok nagy részének olvasására és megjelenítésére. De, mint fentebb említettük, nem érheti el közvetlenül a vizuális összetevőket az adatfolyamból. Hogyan legyen?
A Delphi fejlesztői javasolják a TThread osztály Szinkronizálási módszerének használatát. Itt nem írom le, hogyan kell használni - erre van a fent említett cikk. Hadd mondjam el, hogy alkalmazása, még a helyes is, nem mindig indokolt. Két probléma van:

Először is, a szinkronizáláson keresztül meghívott módszer törzse mindig a fő szál kontextusában kerül végrehajtásra, és ezért végrehajtása közben ismét az ablaküzenet -hurok nem kerül végrehajtásra. Ezért gyorsan kell végrehajtani, különben ugyanazokat a problémákat kapjuk, mint egyszálú megvalósítás esetén. Ideális esetben a Szinkronizáláson keresztül meghívott módszert általában csak a vizuális objektumok tulajdonságainak és módszereinek elérésére szabad használni.

Másodszor, egy módszer szinkronizálással történő végrehajtása "drága" öröm, mivel két szál közötti váltásra van szükség.

Ezenkívül mindkét probléma összekapcsolódik, és ellentmondást okoz: egyrészt az első megoldáshoz szükség van a Szinkronizáláson keresztül meghívott módszerek „őrlésére”, másrészt gyakran meg kell hívni őket, elveszítve az értékes processzor erőforrások.

Ezért, mint mindig, ésszerűen kell megközelíteni, és különböző esetekben különböző módon kell alkalmazni az áramlást a külvilággal:

Kezdeti adatok
Minden adatot, amelyet a folyamba továbbítanak, és működése során nem változik, még az indulás előtt át kell adni, azaz patak létrehozásakor. Ha egy szál törzsében szeretné használni őket, helyi másolatot kell készítenie róluk (általában a TThread leszármazott mezőiben).
Ha vannak olyan kezdeti adatok, amelyek megváltozhatnak a szál futása közben, akkor ezeket az adatokat vagy szinkronizált módszerekkel (a szinkronizáláson keresztül meghívott metódusokkal), vagy a szálobjektum mezőin keresztül kell elérni (a TThread leszármazottja). Ez utóbbi némi óvatosságot igényel.

Közbenső és kimeneti adatok
Itt is többféle módszer létezik (az én preferenciám sorrendjében):
- Az üzenetek aszinkron küldésének módja az alkalmazás főablakába.
Általában a folyamat előrehaladásáról szóló üzeneteket küldik az alkalmazás főablakába, kis mennyiségű adat átvitelével (például a befejezés százalékával)
- Az üzenetek szinkron küldésének módja az alkalmazás főablakába.
Általában ugyanazokra a célokra használják, mint az aszinkron küldés, de lehetővé teszi nagyobb mennyiségű adat továbbítását külön másolat létrehozása nélkül.
- Szinkronizált módszerek, ha lehetséges, a lehető legtöbb adat átvitelét egyetlen módszerbe kombinálva.
Használható adatlapok lekérésére is egy űrlapról.
- A stream objektum mezőin keresztül, kölcsönösen kizáró hozzáférést biztosítva.
További részletek a cikkben találhatók.

Eh. Rövid ideig nem sikerült