U koje se svrhe koriste višeslojni sistemi. Osam jednostavnih pravila za razvoj višenavojnih aplikacija

Koja tema početnicima postavlja najviše pitanja i poteškoća? Kada sam o tome pitao svog učitelja i Java programera Aleksandra Pryakhina, on je odmah odgovorio: „Višestruko ušivanje“. Hvala mu na ideji i pomoći u pripremi ovog članka!

Razmotrit ćemo unutarnji svijet aplikacije i njene procese, shvatiti u čemu je suština multitreadinga, kada je to korisno i kako ga implementirati - koristeći Java kao primjer. Ako učite drugi OOP jezik, ne brinite: osnovni principi su isti.

O potocima i njihovom podrijetlu

Da bismo razumjeli multitreading, prvo shvatimo što je proces. Proces je dio virtualne memorije i resursa koje OS dodjeljuje za pokretanje programa. Ako otvorite nekoliko instanci iste aplikacije, sistem će dodijeliti proces za svaku. U modernim preglednicima za svaku karticu može biti odgovoran zaseban proces.

Vjerojatno ste naišli na Windows "Task Manager" (u Linuxu je to "System Monitor") i znate da nepotrebni pokrenuti procesi opterećuju sistem, a najteži od njih često se zamrzavaju, pa se moraju prisilno prekinuti .

Ali korisnici vole multitasking: nemojte ih hraniti kruhom - neka otvore desetke prozora i skaču naprijed -natrag. Postoji dilema: morate osigurati istovremeni rad aplikacija i istovremeno smanjiti opterećenje sistema kako se ne bi usporilo. Recimo da hardver ne može pratiti potrebe vlasnika - problem morate riješiti na softverskom nivou.

Želimo da procesor izvršava više instrukcija i obrađuje više podataka po jedinici vremena. To jest, moramo u svaki vremenski odsječak uklopiti više izvedenog koda. Jedinicu izvođenja koda zamislite kao objekt - to je nit.

Složenom slučaju lakše je pristupiti ako ga podijelite na nekoliko jednostavnih. Dakle, pri radu s memorijom: "težak" proces podijeljen je na niti koje zauzimaju manje resursa i veća je vjerovatnoća da će kôd isporučiti kalkulatoru (kako tačno - pogledajte dolje).

Svaka aplikacija ima najmanje jedan proces, a svaki proces ima najmanje jednu nit, koja se naziva glavna nit i iz koje se, ako je potrebno, pokreću nove.

Razlika između niti i procesa

    Niti koriste memoriju dodijeljenu procesu, a procesima je potreban vlastiti memorijski prostor. Stoga se niti brže stvaraju i dovršavaju: sistem ne mora svaki put dodijeliti novi adresni prostor, a zatim ga otpustiti.

    Svaki proces radi sa svojim podacima - mogu razmjenjivati ​​nešto samo putem mehanizma međuprocesne komunikacije. Niti pristupaju međusobno podacima i resursima direktno: ono što je promijenjeno odmah je dostupno svima. Nit može kontrolirati "kolegu" u procesu, dok proces kontrolira isključivo svoje "kćeri". Stoga je prebacivanje između tokova brže i komunikacija među njima lakša.

Koji je zaključak iz ovoga? Ako trebate obraditi veliku količinu podataka što je brže moguće, podijelite ih na komade koji se mogu obraditi zasebnim nitima, a zatim spojite rezultat. To je bolje od pokretanja procesa gladnih resursa.

Ali zašto popularna aplikacija poput Firefoxa ide putem stvaranja više procesa? Budući da je za preglednik rad izoliranih kartica pouzdan i fleksibilan. Ako nešto nije u redu s jednim procesom, nije potrebno prekinuti cijeli program - moguće je spremiti barem dio podataka.

Šta je višenavojno

Tako dolazimo do glavne tačke. Multithreading je kada se aplikacijski proces dijeli na niti koje procesor obrađuje paralelno - u jednoj jedinici vremena.

Računarsko opterećenje raspoređeno je između dva ili više jezgara, tako da sučelje i ostale programske komponente ne usporavaju međusobni rad.

Aplikacije s više niti mogu se pokrenuti i na procesorima s jednom jezgrom, ali tada se niti izvode redom: prva je radila, stanje joj je spremljeno - drugoj je dopušteno raditi, spremljeno - vraćeno je na prvu ili je pokrenuto treće, itd.

Zauzeti ljudi žale se da imaju samo dvije ruke. Procesi i programi mogu imati onoliko ruku koliko je potrebno da se zadatak izvrši što je brže moguće.

Sačekajte signal: sinhronizacija u aplikacijama sa više niti

Zamislite da nekoliko niti pokušava promijeniti isto područje podataka u isto vrijeme. Čije će promjene na kraju biti prihvaćene, a čije će biti poništene? Kako bi se izbjegla zabuna sa zajedničkim resursima, niti moraju koordinirati svoje radnje. Da bi to učinili, razmjenjuju informacije pomoću signala. Svaka nit govori drugima šta radi i koje promjene treba očekivati. Dakle, podaci svih niti o trenutnom stanju resursa su sinkronizirani.

Osnovni alati za sinhronizaciju

Međusobno isključenje (međusobno isključivanje, skraćeno - mutex) - "zastavica" koja ide do niti koja je trenutno dopuštena za rad sa zajedničkim resursima. Uklanja pristup drugih niti zauzetom memorijskom području. U aplikaciji može biti nekoliko muteksa i oni se mogu dijeliti između procesa. Postoji zamka: mutex prisiljava aplikaciju da svaki put pristupi jezgri operativnog sistema, što je skupo.

Semafor - dozvoljava vam da ograničite broj niti koje mogu pristupiti resursu u datom trenutku. Ovo će smanjiti opterećenje procesora prilikom izvršavanja koda gdje postoje uska grla. Problem je u tome što optimalan broj niti ovisi o mašini korisnika.

Događaj - definirate stanje nakon kojeg se kontrola prenese na željenu nit. Streamovi razmjenjuju podatke o događajima kako bi se razvili i logički nastavili međusobni postupci. Jedan je primio podatke, drugi je provjerio njihovu ispravnost, treći ih je spremio na tvrdi disk. Događaji se razlikuju po načinu otkazivanja. Ako trebate obavijestiti nekoliko niti o događaju, morat ćete ručno postaviti funkciju otkazivanja da biste zaustavili signal. Ako postoji samo jedna ciljna nit, možete stvoriti događaj automatskog resetiranja. Zaustavit će sam signal nakon što dosegne tok. Događaji se mogu staviti u red za fleksibilnu kontrolu protoka.

Kritički odjeljak - složeniji mehanizam koji kombinira brojač petlji i semafor. Brojač vam omogućuje da odgodite početak semafora za željeno vrijeme. Prednost je što se jezgro aktivira samo ako je odjeljak zauzet i semafor mora biti uključen. Ostatak vremena nit se izvodi u korisničkom načinu rada. Nažalost, odjeljak se može koristiti samo u jednom procesu.

Kako implementirati multithreading u Javi

Klasa Thread je odgovorna za rad sa nitima u Javi. Kreiranje nove niti za izvršavanje zadatka znači stvaranje instance klase Thread i njeno pridruživanje kodu koji želite. To se može učiniti na dva načina:

    potklasa Thread;

    implementirajte Runnable sučelje u svoju klasu, a zatim proslijedite instance klase u Thread konstruktor.

Iako se nećemo doticati teme zastoja, kada niti blokiraju međusobni rad i zamrznu, to ćemo ostaviti za sljedeći članak.

Primjer višenavojnog Java -a: ping pong sa muteksima

Ako mislite da će se nešto strašno dogoditi, izdahnite. Rad sa sinhronizacijskim objektima razmotrit ćemo gotovo na šaljiv način: dvije niti će biti izbačene muteksom, ali u stvari ćete vidjeti stvarnu aplikaciju u kojoj samo jedna nit može odjednom obraditi javno dostupne podatke.

Prvo, kreirajmo klasu koja nasljeđuje svojstva niti koju već poznajemo i napišemo kickBall metodu:

Javna klasa PingPongThread proširuje Thread (PingPongThread (String name) (this.setName (name); // nadjačava naziv niti) @Override public void run () (Ball ball = Ball.getBall (); while (ball.isInGame () ) (kickBall (ball);)) private void kickBall (Ball ball) (if (! ball.getSide (). equals (getName ())) (ball.kick (getName ());)))

Sada se pobrinimo za loptu. S nama neće biti jednostavan, već pamtljiv: tako da može reći ko ga je udario, s koje strane i koliko puta. Da bismo to učinili, koristimo mutex: on će prikupljati informacije o radu svake niti - to će omogućiti izoliranim nitima da međusobno komuniciraju. Nakon 15. pogotka izvadit ćemo loptu iz igre, kako je ne bismo ozbiljno ozlijedili.

Javna klasa Ball (private int kicks = 0; private static Ball instance = new Ball (); private String side = ""; private Ball () () static Ball getBall () (return instance;) sinhronizovano poništavanje udarca (String playername) (kicks ++; side = playername; System.out.println (kicks + "" + side);) String getSide () (return side;) boolean isInGame () (return (kicks< 15); } }

I sada dvije niti igrača ulaze na scenu. Nazovimo ih, bez odlaganja, Ping i Pong:

Javna klasa PingPongGame (PingPongThread player1 = novi PingPongThread ("Ping"); PingPongThread player2 = novi PingPongThread ("Pong"); Ball ball; PingPongGame () (ball = Ball.getBall ();) void startGame1 (bacanje prekida) .start (); player2.start ();))

"Pun stadion ljudi - vrijeme je za početak utakmice." Službeno ćemo najaviti otvaranje sastanka - u glavnoj klasi aplikacije:

Javna klasa PingPong (javna statička void main (String args) baca InterruptException (PingPongGame igra = nova PingPongGame (); game.startGame ();))

Kao što vidite, ovdje nema ničeg bijesnog. Ovo je za sada samo uvod u višestruko nitanje, ali već znate kako to funkcionira i možete eksperimentirati - ograničite trajanje igre ne brojem poteza, na primjer, vremenom. Kasnije ćemo se vratiti na temu multitreadinga - pogledaćemo java.util.concurrent paket, Akka biblioteku i nestabilni mehanizam. Razgovarajmo i o implementaciji multitreadinga u Pythonu.

Programiranje sa više niti se bitno ne razlikuje od pisanja grafičkih korisničkih interfejsa zasnovanih na događajima ili čak pisanja jednostavnih sekvencijalnih aplikacija. Ovdje se primjenjuju sva važna pravila koja reguliraju inkapsulaciju, odvajanje briga, labavo spajanje itd. No, mnogim programerima je teško pisati višeslojne programe upravo zato što zanemaruju ta pravila. Umjesto toga, oni pokušavaju u praksi primijeniti mnogo manje važno znanje o nitima i primitivima sinhronizacije, prikupljeno iz tekstova o programiranju s više niti za početnike.

Pa koja su to pravila

Drugi programer, suočen sa problemom, misli: "Oh, tačno, moramo primijeniti regularne izraze." I sada već ima dva problema - Jamie Zawinski.

Drugi programer, suočen s problemom, misli: "Oh, dobro, ovdje ću koristiti streamove." I sada ima deset problema - Bill Schindler.

Previše programera koji se obavežu da pišu višenamjenski kod upadne u zamku, poput junaka Geteove balade " Čarobnjakov učenik". Programer će naučiti kako stvoriti hrpu niti koje, u principu, funkcioniraju, ali prije ili kasnije izmaknu kontroli, a programer ne zna što treba učiniti.

No, za razliku od ispadanja čarobnjaka, nesretni programer ne može se nadati dolasku moćnog čarobnjaka koji će mahati štapićem i uspostaviti red. Umjesto toga, programer ide na najneprijatnije trikove, pokušavajući se nositi s problemima koji se stalno pojavljuju. Rezultat je uvijek isti: dobiva se previše komplicirana, ograničena, krhka i nepouzdana aplikacija. Ima stalnu prijetnju zastoja i druge opasnosti svojstvene lošem višenavojnom kodu. Ne govorim čak ni o neobjašnjivim padovima, lošim performansama, nepotpunim ili netačnim rezultatima rada.

Možda ste se pitali: zašto se to događa? Uobičajena zabluda je: "Programiranje s više niti je vrlo teško." Ali to nije slučaj. Ako je višenamjenski program nepouzdan, tada obično ne uspije iz istih razloga kao i nekvalitetni jednonavojni program. Samo što programer ne slijedi temeljne, dobro poznate i provjerene razvojne metode. Čini se da su višeslojni programi samo složeniji, jer što više paralelnih niti griješi, to više stvaraju nered - i mnogo brže nego što bi to učinila jedna nit.

Zabluda o "složenosti višenavojnog programiranja" postala je široko rasprostranjena zbog onih programera koji su se profesionalno razvili u pisanju jednonavojnog koda, prvi put su se susreli s višeslojnim i nisu se nosili s tim. No, umjesto da preispitaju svoje predrasude i radne navike, tvrdoglavo popravljaju činjenicu da ni na koji način ne žele raditi. Opravdavajući se zbog nepouzdanog softvera i propuštenih rokova, ovi ljudi ponavljaju istu stvar: "programiranje s više niti je vrlo teško."

Imajte na umu da gore govorim o tipičnim programima koji koriste multitreading. Zaista, postoje složeni višenamjenski scenariji-kao i složeni jednonavojni. Ali oni nisu uobičajeni. Po pravilu, u praksi se od programera ne traži ništa natprirodno. Premještamo podatke, transformiramo ih, s vremena na vrijeme vršimo neke proračune i na kraju spremamo podatke u bazu podataka ili ih prikazujemo na ekranu.

Nema ništa teško u poboljšanju prosječnog jednonavojnog programa i pretvaranju u višenavojni. Bar ne bi trebalo biti. Poteškoće nastaju iz dva razloga:

  • programeri ne znaju kako primijeniti jednostavne, dobro poznate provjerene razvojne metode;
  • većina informacija predstavljenih u knjigama o programiranju s više niti tehnički je ispravna, ali potpuno neprimjenjiva za rješavanje primijenjenih problema.

Najvažniji programski koncepti su univerzalni. Jednako su primjenjivi na jednonavojne i višenavojne programe. Programeri koji se dave u vrtlogu tokova jednostavno nisu naučili važne lekcije kada su savladali jednonavojni kod. Mogu to reći jer takvi programeri čine iste temeljne greške u višenamjenskim i jednonavojnim programima.

Možda najvažnija lekcija koju treba naučiti u šezdeset godina istorije programiranja je: globalno promjenjivo stanje- zlo... Pravo zlo. O programima koji se oslanjaju na globalno promjenjivo stanje relativno je teško razmišljati i općenito su nepouzdani jer postoji previše načina za promjenu stanja. Bilo je mnogo studija koje potvrđuju ovaj opći princip, postoji bezbroj dizajnerskih obrazaca čiji je glavni cilj implementirati na ovaj ili onaj način skrivanje podataka. Da biste svoje programe učinili predvidljivijima, pokušajte ukloniti promjenjivo stanje što je više moguće.

U jednom sekvencijalnom programu sa jednom niti, vjerovatnoća oštećenja podataka direktno je proporcionalna broju komponenti koje mogu promijeniti podatke.

U pravilu nije moguće potpuno se riješiti globalnog stanja, ali programer u svom arsenalu ima vrlo učinkovite alate koji vam omogućuju da strogo kontrolirate koje programske komponente mogu promijeniti stanje. Osim toga, naučili smo kako stvoriti restriktivne API slojeve oko primitivnih struktura podataka. Stoga imamo dobru kontrolu nad promjenom ovih struktura podataka.

Problemi globalno promjenjivog stanja postepeno su postali jasni kasnih 80-ih i ranih 90-ih, s porastom programa zasnovanog na događajima. Programi se više nisu pokretali "od početka" ili su slijedili jednu, predvidljivu putanju izvršenja "do kraja". Moderni programi imaju početno stanje, nakon izlaska iz kojeg se događaju događaji u njima - nepredvidivim redoslijedom, s promjenjivim vremenskim intervalima. Kod ostaje jednonavojni, ali već postaje asinhroni. Vjerovatnoća oštećenja podataka raste upravo zato što je redoslijed događaja vrlo važan. Ovakve su situacije prilično česte: ako se događaj B dogodi nakon događaja A, onda sve funkcionira u redu. Ali ako se događaj A dogodi nakon događaja B, a događaj C ima vremena intervenirati između njih, tada se podaci mogu izobličiti do neprepoznatljivosti.

Ako su uključeni paralelni tokovi, problem se dodatno pogoršava, budući da nekoliko metoda može istovremeno djelovati na globalno stanje. Postaje nemoguće procijeniti kako se globalno stanje mijenja. Već govorimo ne samo o činjenici da se događaji mogu dogoditi nepredvidivim redoslijedom, već i o činjenici da se stanje nekoliko niti izvršavanja može ažurirati. istovremeno... Pomoću asinhronog programiranja možete u najmanju ruku osigurati da se određeni događaj ne može dogoditi prije nego što drugi događaj završi obradu. Odnosno, sa sigurnošću je moguće reći kakvo će globalno stanje biti na kraju obrade određenog događaja. U višenavojnom kodu u pravilu je nemoguće reći koji će se događaji paralelno dogoditi, pa je nemoguće sa sigurnošću opisati globalno stanje u bilo kojem trenutku.

Višenamjenski program s opsežnim globalno promjenjivim stanjem jedan je od najrječitijih primjera Heisenbergovog principa nesigurnosti za koji znam. Nemoguće je provjeriti stanje programa bez promjene njegovog ponašanja.

Kad započnem još jedan filip o globalnom promjenjivom stanju (suština je izložena u nekoliko prethodnih odlomaka), programeri prevrću očima i uvjeravaju me da sve to znaju već duže vrijeme. Ali ako znate ovo, zašto ne možete zaključiti iz svog koda? Programi su pretrpani globalnim promjenjivim stanjem, a programeri se pitaju zašto kod ne radi.

Nije iznenađujuće da se najvažniji posao u višeslojnom programiranju događa u fazi projektiranja. Potrebno je jasno definirati što program treba učiniti, razviti nezavisne module za obavljanje svih funkcija, detaljno opisati koji su podaci potrebni za koji modul i odrediti načine razmjene informacija između modula ( Da, ne zaboravite pripremiti lijepe majice za sve koji su uključeni u projekt. Prva stvar.- cca. ed. u originalu). Ovaj proces se bitno ne razlikuje od dizajniranja jednonavojnog programa. Ključ uspjeha, kao i kod sa jednim navojem, je ograničenje interakcije između modula. Ako se možete riješiti zajedničkog promjenjivog stanja, problemi s dijeljenjem podataka jednostavno neće nastati.

Netko bi mogao tvrditi da ponekad nema vremena za tako delikatan dizajn programa, koji će omogućiti da se opstane bez globalne države. Vjerujem da je moguće i potrebno potrošiti vrijeme na to. Ništa ne utječe na višenavojne programe tako destruktivno kao pokušaj suočavanja sa globalnim promjenjivim stanjem. Što više detalja morate da upravljate, veća je verovatnoća da će vaš program dostići vrhunac i srušiti se.

U realnim aplikacijama mora postojati neka vrsta zajedničkog stanja koje se može promijeniti. I tu većina programera počinje imati problema. Programer vidi da je ovdje potrebno zajedničko stanje, okreće se višenavojnom arsenalu i odatle uzima najjednostavniji alat: univerzalnu bravu (kritični odjeljak, mutex ili kako god to nazvali). Čini se da vjeruju da će međusobno isključivanje riješiti sve probleme razmjene podataka.

Broj problema koji mogu nastati s ovakvom jednom bravom je zapanjujući. Treba uzeti u obzir uvjete utrke, probleme rješavanja sa previše opsežnim blokiranjem, a pitanja pravičnosti raspodjele samo su neki primjeri. Ako imate više zaključavanja, posebno ako su ugniježđena, tada ćete također morati poduzeti mjere protiv zastoja, dinamičkog zastoja, blokiranja redova i drugih prijetnji povezanih s istovremenošću. Osim toga, postoje inherentni problemi pojedinačnog blokiranja.
Kad pišem ili pregledavam kod, imam gotovo nepogrešivo željezno pravilo: ako ste zaključali, čini se da ste negdje pogriješili.

Ova izjava se može komentirati na dva načina:

  1. Ako vam je potrebno zaključavanje, vjerojatno imate globalno promjenjivo stanje koje želite zaštititi od istovremenih ažuriranja. Prisustvo globalnog promjenjivog stanja je nedostatak u fazi dizajniranja aplikacije. Pregledajte i redizajnirajte.
  2. Pravilno korištenje brava nije lako, a može biti nevjerojatno teško lokalizirati greške povezane sa zaključavanjem. Vrlo je vjerojatno da ćete zaključavanje koristiti pogrešno. Ako vidim zaključavanje, a program se ponaša na neobičan način, prvo što moram učiniti je provjeriti kôd koji ovisi o zaključavanju. I obično nalazim probleme u tome.

Obje ove interpretacije su tačne.

Pisanje višenavojnog koda je jednostavno. No, vrlo je teško pravilno koristiti primitive sinhronizacije. Možda niste kvalificirani za pravilno korištenje čak ni jedne brave. Na kraju krajeva, brave i drugi primitivi sinhronizacije su konstrukcije koje su podignute na nivou cijelog sistema. Ljudi koji razumiju paralelno programiranje mnogo bolje od vas koriste ove primitive za izgradnju istovremenih struktura podataka i konstrukcija za sinkronizaciju na visokom nivou. A ti i ja, obični programeri, samo uzimamo takve konstrukcije i koristimo ih u našem kodu. Programer za aplikacije ne bi trebao češće koristiti primitive sinhronizacije na niskom nivou nego što upućuje direktne pozive upravljačkim programima uređaja. Odnosno, skoro nikada.

Pokušaj korištenja brave za rješavanje problema razmjene podataka je poput gašenja požara tekućim kisikom. Poput požara, takve probleme je lakše spriječiti nego popraviti. Ako se riješite dijeljenog stanja, ne morate zloupotrijebiti niti primitive sinhronizacije.

Većina onoga što znate o višestrukim nitima nije važno

U višenamjenskim vodičima za početnike naučit ćete što su niti. Zatim će autor početi razmatrati različite načine na koje ove niti mogu raditi paralelno - na primjer, govoriti o kontroli pristupa zajedničkim podacima pomoću zaključavanja i semafora, te će se zadržati na tome šta se može dogoditi pri radu s događajima. Pomno će pogledati varijable stanja, memorijske barijere, kritične sekcije, mutekse, promjenjiva polja i atomske operacije. Bit će razmotreni primjeri kako koristiti ove konstrukcije na niskom nivou za izvođenje svih vrsta sistemskih operacija. Nakon što je do pola pročitao ovaj materijal, programer odlučuje da već dovoljno zna o svim tim primitivcima i njihovoj upotrebi. Uostalom, ako znam kako ova stvar funkcionira na sistemskom nivou, mogu je primijeniti na isti način na nivou aplikacije. Da?

Zamislite da tinejdžeru kažete kako da sam sastavi motor s unutrašnjim sagorijevanjem. Zatim, bez ikakve obuke u vožnji, stavite ga za volan automobila i kažete: "Idi!" Tinejdžer razumije kako automobil funkcionira, ali nema pojma kako doći od tačke A do tačke B na njemu.

Razumijevanje načina rada niti na sistemskoj razini obično ne pomaže ni na koji način na razini aplikacije. Ne sugeriram da programeri ne moraju naučiti sve ove detalje na niskom nivou. Samo nemojte očekivati ​​da ćete ovo znanje moći primijeniti odmah pri dizajniranju ili razvoju poslovne aplikacije.

Uvodna literatura o nitima (i srodni akademski predmeti) ne bi trebala istraživati ​​takve konstrukcije niskog nivoa. Morate se usredotočiti na rješavanje najčešćih klasa problema i pokazati programerima kako se ti problemi rješavaju pomoću mogućnosti na visokom nivou. U principu, većina poslovnih aplikacija su izuzetno jednostavni programi. Oni čitaju podatke s jednog ili više uređaja za unos podataka, izvode složene obrade tih podataka (na primjer, u procesu zahtijevaju još neke podatke), a zatim ispisuju rezultate.

Ovi programi se često savršeno uklapaju u model dobavljač-potrošač, koji zahtijeva samo tri niti:

  • ulazni tok čita podatke i stavlja ih na ulazni red;
  • radnička nit čita zapise iz ulaznog reda, obrađuje ih i stavlja rezultate u izlazni red;
  • izlazni tok čita unose iz izlaznog reda i sprema ih.

Ove tri niti rade neovisno, komunikacija između njih odvija se na razini reda.

Iako se tehnički redovi tehnički mogu smatrati zonama zajedničkog stanja, u praksi su to samo komunikacijski kanali u kojima funkcionira njihova vlastita interna sinhronizacija. Redovi podržavaju rad s mnogim proizvođačima i potrošačima odjednom, a paralelno im možete dodavati i uklanjati stavke.

Budući da su faze unosa, obrade i izlaza međusobno izolirane, njihova se implementacija može lako promijeniti bez utjecaja na ostatak programa. Sve dok se vrsta podataka u redu ne promijeni, možete po vlastitom nahođenju refaktorirati pojedinačne komponente programa. Osim toga, budući da u redu sudjeluje proizvoljan broj dobavljača i potrošača, nije teško dodati druge proizvođače / potrošače. Možemo imati desetine ulaznih tokova koji upisuju informacije u isti red, ili desetine niti radnika koje uzimaju informacije iz ulaznog reda i vare podatke. U okviru jednog računara, takav model se dobro skalira.

Ono što je najvažnije, moderni programski jezici i biblioteke olakšavaju stvaranje aplikacija proizvođača-potrošača. U .NET -u ćete pronaći paralelne zbirke i TPL biblioteku protoka podataka. Java ima uslugu Executor, kao i BlockingQueue i druge klase iz prostora imena java.util.concurrent. C ++ ima biblioteku Boost threadinga i Intelovu biblioteku Thread Building Blocks. Microsoftov Visual Studio 2013 predstavlja asinhrone agente. Slične biblioteke su takođe dostupne u Pythonu, JavaScript -u, Ruby -u, PHP -u ​​i, koliko ja znam, mnogim drugim jezicima. Možete stvoriti aplikaciju proizvođač-potrošač koristeći bilo koji od ovih paketa, bez potrebe da pribjegavate zaključavanju, semaforima, varijablama uvjeta ili bilo kojim drugim primitivima za sinkronizaciju.

U tim se bibliotekama slobodno koristi veliki broj primitiva za sinkronizaciju. Ovo je u redu. Sve ove biblioteke pišu ljudi koji se razumiju u višeslojnost neuporedivo bolje od prosječnog programera. Rad s takvom bibliotekom praktički je isti kao i korištenje biblioteke jezika za vrijeme izvođenja. Ovo se može uporediti sa programiranjem na jeziku višeg nivoa, a ne na asemblerskom jeziku.

Model dobavljač-potrošač samo je jedan od mnogih primjera. Gore navedene biblioteke sadrže klase koje se mogu koristiti za implementaciju mnogih uobičajenih obrazaca dizajna niti bez ulaženja u detalje na niskom nivou. Moguće je stvarati velike višeslojne aplikacije bez brige o tome kako su niti usklađene i sinhronizirane.

Rad sa bibliotekama

Dakle, stvaranje višenavojnih programa ne razlikuje se bitno od pisanja sinkronih programa s jednim navojem. Važni principi enkapsulacije i skrivanja podataka univerzalni su i postaju važni samo ako je uključeno više istovremenih niti. Ako zanemarite ove važne aspekte, neće vas spasiti ni najopsežnije znanje o niskim nitima.

Savremeni programeri moraju riješiti mnogo problema na nivou aplikativnog programiranja, dešava se da jednostavno nema vremena za razmišljanje o tome šta se dešava na sistemskom nivou. Što aplikacije postaju zamršenije, složeniji detalji moraju biti skriveni između nivoa API -ja. To radimo više od desetak godina. Može se reći da je kvalitativno skrivanje složenosti sistema od programera glavni razlog zašto programer može pisati moderne aplikacije. Što se toga tiče, ne skrivamo li složenost sistema implementacijom petlje UI poruka, izgradnjom komunikacijskih protokola na niskom nivou itd.?

Slična je situacija i s višedijelnošću. Većina scenarija sa više niti sa kojima bi se prosječni programer poslovnih aplikacija mogao susresti već je dobro poznat i dobro implementiran u bibliotekama. Bibliotečke funkcije odlično rade prikrivajući ogromnu složenost paralelizma. Morate naučiti kako koristiti ove biblioteke na isti način na koji koristite biblioteke elemenata korisničkog sučelja, komunikacijske protokole i brojne druge alate koji jednostavno rade. Prepustite višestruko nitanje na niskom nivou stručnjacima - autorima biblioteka koje se koriste pri kreiranju aplikacija.

NS Ovaj članak nije za iskusne krotitelje Pythona, za koje je razotkrivanje ove zmijske kugle dječja igra, već površan pregled mogućnosti višestrukih niti za novozavisnog pitona.

Nažalost, nema toliko materijala na ruskom jeziku o temi višestrukosti u Pythonu, a pythoneri koji nisu čuli ništa, na primjer, o GIL -u, počeli su mi se javljati sa zavidnom pravilnošću. U ovom članku pokušat ću opisati najosnovnije značajke višeslojnog pythona, reći vam što je GIL i kako živjeti s njim (ili bez njega), i još mnogo toga.


Python je šarmantan programski jezik. Savršeno kombinira mnoge programske paradigme. Većina zadataka koje programer može zadovoljiti rješavaju se ovdje lako, elegantno i sažeto. No za sve ove probleme rješenje s jednim niti često je dovoljno, a programi s jednim navojem obično su predvidljivi i lako ih je otkloniti pogreške. Isto se ne može reći za višeslojne i višeprocesne programe.

Višenavojne aplikacije


Python ima modul threading , i ima sve što vam je potrebno za programiranje s više niti: postoje različite vrste zaključavanja, semafor i mehanizam događaja. Jednom riječju - sve što je potrebno za veliku većinu višeslojnih programa. Štoviše, korištenje svih ovih alata prilično je jednostavno. Razmotrimo primjer programa koji pokreće 2 niti. Jedna nit piše deset "0", druga - deset "1" i strogo redom.

import threading

def writer

za ja u xrangeu (10):

ispis x

Event_for_set.set ()

# početnih događaja

e1 = threading.Event ()

e2 = threading.Event ()

# init niti

0, e1, e2))

1, e2, e1))

# početnih niti

t1.start ()

t2.start ()

t1.join ()

t2.join ()


Bez magije ili vudu koda. Kôd je jasan i dosljedan. Štaviše, kao što vidite, stvorili smo tok iz funkcije. Ovo je vrlo prikladno za male zadatke. Ovaj kod je također prilično fleksibilan. Pretpostavimo da imamo treći proces koji piše “2”, tada će kod izgledati ovako:

import threading

def writer (x, event_for_wait, event_for_set):

za ja u xrangeu (10):

Event_for_wait.wait () # čekanje na događaj

Event_for_wait.clear () # čisti događaj za budućnost

ispis x

Event_for_set.set () # set događaj za susjednu nit

# početnih događaja

e1 = threading.Event ()

e2 = threading.Event ()

e3 = threading.Event ()

# init niti

t1 = threading.Thread (cilj = pisac, args = ( 0, e1, e2))

t2 = threading.Thread (cilj = pisac, args = ( 1, e2, e3))

t3 = threading.Thread (cilj = pisac, args = ( 2, e3, e1))

# početnih niti

t1.start ()

t2.start ()

t3.start ()

e1.set () # pokreće prvi događaj

# pridruživanje niti glavnoj niti

t1.join ()

t2.join ()

t3.join ()


Dodali smo novi događaj, novu nit i malo promijenili parametre s kojima
pokreću se streamovi (naravno, možete napisati općenitije rješenje koristeći, na primjer, MapReduce, ali to je izvan opsega ovog članka).
Kao što vidite, još nema magije. Sve je jednostavno i razumljivo. Idemo dalje.

Globalno zaključavanje tumača


Postoje dva najčešća razloga za korištenje niti: prvo, za povećanje efikasnosti korištenja višejezgrene arhitekture modernih procesora, a time i za performanse programa;
drugo, ako moramo podijeliti logiku programa na paralelne, potpuno ili djelomično asinhrone sekcije (na primjer, da bismo mogli pingati više servera istovremeno).

U prvom slučaju, suočeni smo s takvim ograničenjem Pythona (ili bolje rečeno njegovom glavnom implementacijom CPythona) kao što je Global Interpreter Lock (ili skraćeno GIL). Koncept GIL -a je da samo jednu nit može izvršiti procesor u isto vrijeme. To se radi kako ne bi došlo do borbe između niti za odvojene varijable. Izvršna nit dobija pristup čitavom okruženju. Ova značajka implementacije niti u Pythonu uvelike pojednostavljuje rad s nitima i daje određenu sigurnost niti.

No, postoji suptilna stvar: moglo bi se činiti da će višenamjenska aplikacija izvoditi točno onoliko vremena koliko i jednonitna aplikacija radi isto, ili zbir vremena izvršavanja svake niti na CPU-u. Ali ovdje nas čeka jedan neugodan efekt. Razmotrite program:

s otvorenim ("test1.txt", "w") kao fout:

za i in xrange (1000000):

ispis >> fout, 1


Ovaj program samo zapisuje milion redova “1” u datoteku i to čini za ~ 0,35 sekundi na mom računaru.

Razmislite o drugom programu:

from threading import Thread

def pisac (naziv datoteke, n):

s otvorenim (naziv datoteke, "w") kao fout:

za i u xrange (n):

ispis >> fout, 1

t1 = Nit (cilj = pisac, args = ("test2.txt", 500000,))

t2 = Nit (cilj = pisac, args = ("test3.txt", 500000,))

t1.start ()

t2.start ()

t1.join ()

t2.join ()


Ovaj program stvara 2 niti. U svakoj niti upisuje u zasebnu datoteku pola miliona redova "1". Zapravo, količina posla je ista kao u prethodnom programu. No, s vremenom se ovdje postiže zanimljiv učinak. Program može raditi od 0,7 sekundi do čak 7 sekundi. Zašto se to događa?

To je zbog činjenice da kada niti ne treba CPU resurs, oslobađa GIL, i u tom trenutku može pokušati dobiti, i drugu nit, a također i glavnu nit. U isto vrijeme, operativni sistem, znajući da postoji mnogo jezgri, može sve pogoršati pokušavajući distribuirati niti između jezgri.

UPD: trenutno, u Pythonu 3.2, postoji poboljšana implementacija GIL -a, u kojoj je ovaj problem djelomično riješen, posebno zbog činjenice da svaka nit, nakon što izgubi kontrolu, čeka kratko vrijeme prije nego može ponovo snimiti GIL (postoji dobra prezentacija na engleskom)

„Znači, ne možete pisati efikasne višeslojne programe u Pythonu?“ Pitate. Ne, naravno, postoji izlaz, pa čak i nekoliko.

Višeprocesne aplikacije


Kako bi na neki način riješio problem opisan u prethodnom paragrafu, Python ima modul potproces ... Možemo napisati program koji želimo izvesti u paralelnoj niti (u stvari, to je već proces). I pokrenite ga u jednoj ili više niti u drugom programu. Ovo bi zaista ubrzalo naš program, jer se niti stvorene u pokretaču GIL -a ne hvataju, već samo čekaju da se pokrenuti proces završi. Međutim, ova metoda ima dosta problema. Glavni problem je što postaje teško prenositi podatke između procesa. Morali biste nekako serijalizirati objekte, uspostaviti komunikaciju putem PIPE -a ili drugih alata, ali sve to neizbježno nosi smetnje i kôd postaje teško razumljiv.

Ovdje nam može pomoći još jedan pristup. Python ima višeprocesorski modul ... U smislu funkcionalnosti, ovaj modul podsjeća threading ... Na primjer, procesi se mogu stvoriti na isti način iz redovnih funkcija. Metode rada s procesima su gotovo iste kao i za niti iz modula niti. No, uobičajeno je koristiti druge alate za sinkronizaciju procesa i razmjenu podataka. Govorimo o redovima (Queue) i cijevima (Pipe). Međutim, ovdje se nalaze i analozi zaključavanja, događaja i semafora koji su bili u nitima.

Osim toga, višeprocesni modul ima mehanizam za rad sa zajedničkom memorijom. U tu svrhu, modul ima klase varijabli (Value) i niz (Array), koje se mogu “dijeliti” između procesa. Za praktičnost rada sa zajedničkim varijablama, možete koristiti klase menadžera. Fleksibilniji su i lakši za upotrebu, ali sporiji. Treba napomenuti da postoji lijepa prilika za stvaranje uobičajenih tipova iz modula ctypes koristeći modul multiprocessing.sharedctypes.

Također u višeprocesnom modulu postoji mehanizam za stvaranje procesnih spremišta. Ovaj mehanizam je vrlo zgodan za upotrebu za implementaciju uzorka majstor-radnik ili za implementaciju paralelne karte (što je u određenom smislu poseban slučaj majstora-radnika).

Od glavnih problema u radu s višeprocesorskim modulom, valja istaknuti relativnu ovisnost ovog modula o platformi. Budući da je rad s procesima različito organiziran u različitim operativnim sistemima, određena ograničenja nameću se kodu. Na primjer, Windows nema mehanizam vilice, pa se točka razdvajanja procesa mora umotati u:

if __name__ == "__main__":


Međutim, ovaj dizajn je već dobra forma.

Šta još...


Postoje i druge biblioteke i pristupi za pisanje paralelnih aplikacija u Pythonu. Na primjer, možete koristiti Hadoop + Python ili različite Python MPI implementacije (pyMPI, mpi4py). Možete čak koristiti omote postojećih C ++ ili Fortran biblioteka. Ovdje se mogu spomenuti takvi okviri / biblioteke kao što su Pyro, Twisted, Tornado i mnogi drugi. Ali sve ovo već izlazi iz okvira ovog članka.

Ako vam se svidio moj stil, u sljedećem članku pokušat ću vam reći kako napisati jednostavne tumače u PLY i za šta se mogu koristiti.

Poglavlje 10.

Višenavojne aplikacije

Multitasking u modernim operativnim sistemima uzima se zdravo za gotovo [ Prije pojave Apple OS X na Macintosh računarima nije bilo modernih višezadaćnih operativnih sistema. Vrlo je teško pravilno dizajnirati operativni sistem sa punopravnim multitaskingom, pa je OS X morao biti zasnovan na Unix sistemu.]. Korisnik očekuje da se pri istovremenom pokretanju uređivača teksta i klijenta e-pošte ti programi neće sukobiti, a pri primanju e-pošte uređivač neće prestati raditi. Kada se istovremeno pokrene više programa, operativni sistem se brzo prebacuje između programa, pružajući im redom procesor (osim ako, naravno, na računaru nije instalirano više procesora). Kao rezultat, iluzija pokretanje više programa istovremeno, jer čak ni najbolji daktilograf (i najbrža internetska veza) ne mogu pratiti savremeni procesor.

Multithreading se u određenom smislu može posmatrati kao sljedeći nivo višezadaćnosti: umjesto prebacivanja između različitih programi, operativni sistem se prebacuje između različitih dijelova istog programa. Na primjer, klijent e-pošte s više niti omogućuje vam primanje novih poruka e-pošte dok čitate ili sastavljate nove poruke. U današnje vrijeme mnogi korisnici također prihvaćaju višestruko niti kao zdravo za gotovo.

VB nikada nije imao normalnu podršku za više niti. Istina, jedna od njegovih sorti pojavila se u VB5 - model kolaborativnog strujanja(urezivanje stana). Kao što ćete uskoro vidjeti, kolaborativni model pruža programeru neke od prednosti višeslojnosti, ali ne koristi u potpunosti sve mogućnosti. Prije ili kasnije morate prijeći sa stroja za vježbanje na pravi, a VB .NET je postao prva verzija VB -a s podrškom za besplatni višeslojni model.

Međutim, višestruko nitanje nije jedna od značajki koje se lako implementiraju u programske jezike i kojima programeri lako savladavaju. Zašto?

Budući da se u višeslojnim aplikacijama mogu pojaviti vrlo škakljive greške koje se pojavljuju i nestaju nepredvidivo (a takve greške je najteže otkloniti).

Iskreno upozorenje: višestruko nitanje jedno je od najtežih područja programiranja. Najmanja nepažnja dovodi do pojave neuhvatljivih grešaka za čije ispravljanje su potrebne astronomske svote. Iz tog razloga ovo poglavlje sadrži mnoge loše primjere - namjerno smo ih napisali na način da pokažemo uobičajene greške. Ovo je najsigurniji pristup učenju programiranja s više niti: morate biti u mogućnosti uočiti potencijalne probleme kada na prvi pogled izgleda da sve funkcionira kako treba i znati ih riješiti. Ako želite koristiti tehnike programiranja s više niti, ne možete bez toga.

Ovo poglavlje će postaviti čvrste temelje za daljnji neovisni rad, ali nećemo moći opisati višeslojno programiranje u svim zamršenostima - samo štampana dokumentacija o klasama prostora imena Threading zauzima više od 100 stranica. Ako želite savladati višeslojno programiranje na višem nivou, pogledajte specijalizirane knjige.

Bez obzira na to koliko je programiranje s više niti opasno, ono je neophodno za profesionalno rješavanje nekih problema. Ako vaši programi ne koriste multithreading gdje je to potrebno, korisnici će postati jako frustrirani i preferirat će drugi proizvod. Na primjer, tek u četvrtoj verziji popularnog programa za e-poštu Eudora pojavile su se višeslojne mogućnosti, bez kojih je nemoguće zamisliti bilo koji moderan program za rad s e-poštom. Do trenutka kada je Eudora uvela podršku za više niti, mnogi korisnici (uključujući jednog od autora ove knjige) prešli su na druge proizvode.

Konačno, u .NET-u jednonavojni programi jednostavno ne postoje. Sve.NET programi su višedvojni jer sakupljač smeća radi kao pozadinski proces niskog prioriteta. Kao što je dolje prikazano, za ozbiljno grafičko programiranje u .NET -u, pravilno rezanje niti može spriječiti blokiranje grafičkog sučelja kada program izvršava dugotrajne operacije.

Predstavljamo multithreading

Svaki program radi u određenom obliku kontekst, opisuje distribuciju koda i podataka u memoriji. Kada spremite kontekst, stanje tijeka programa se zapravo sprema, što vam omogućuje da ga vratite u budućnosti i nastavite s izvršavanjem programa.

Čuvanje konteksta dolazi s troškom vremena i memorije. Operativni sistem pamti stanje programske niti i prenosi kontrolu na drugu nit. Kada program želi nastaviti s izvršavanjem suspendirane niti, spremljeni kontekst se mora vratiti, što traje još duže. Stoga se višeslojni tok treba koristiti samo kada koristi nadoknađuju sve troškove. Neki tipični primjeri navedeni su u nastavku.

  • Funkcionalnost programa jasno je i prirodno podijeljena na nekoliko heterogenih operacija, kao u primjeru s primanjem e-pošte i pripremom novih poruka.
  • Program izvodi dugačke i složene proračune i ne želite da se grafičko sučelje blokira za vrijeme izračuna.
  • Program radi na višeprocesorskom računaru sa operativnim sistemom koji podržava upotrebu više procesora (sve dok broj aktivnih niti ne prelazi broj procesora, paralelno izvođenje je praktično bez troškova povezanih sa prebacivanjem niti).

Prije nego što pređemo na mehaniku višeslojnih programa, potrebno je ukazati na jednu okolnost koja često izaziva zabunu kod početnika u području višeslojnog programiranja.

U toku programa izvodi se procedura, a ne objekt.

Teško je reći što se podrazumijeva pod izrazom "objekt se izvršava", ali jedan od autora često drži seminare o programiranju s više niti, a ovo se pitanje postavlja češće od drugih. Možda neko misli da rad programske niti počinje pozivom na novu metodu klase, nakon čega nit obrađuje sve poruke proslijeđene odgovarajućem objektu. Takvi prikazi apsolutno greše. Jedan objekt može sadržavati nekoliko niti koje izvode različite (a ponekad čak i iste) metode, dok se poruke objekta prenose i primaju iz više različitih niti (usput, ovo je jedan od razloga koji kompliciraju programiranje s više niti: da biste otklonili greške u programu, morate saznati koja nit u datom trenutku izvodi ovu ili onu proceduru!).

Budući da se niti kreiraju iz metoda objekata, sam objekt se obično kreira prije niti. Nakon uspješnog kreiranja objekta, program kreira nit, prosljeđujući joj adresu metode objekta i tek nakon toga daje nalog za početak izvođenja niti. Postupak za koji je nit kreirana, kao i sve procedure, može stvarati nove objekte, izvoditi operacije na postojećim objektima i pozivati ​​druge procedure i funkcije koje su u njegovom opsegu.

Uobičajene metode klasa se takođe mogu izvoditi u nitima programa. U ovom slučaju imajte na umu i drugu važnu okolnost: nit završava izlaskom iz procedure za koju je stvorena. Normalan završetak programskog toka nije moguć sve dok se postupak ne izađe.

Niti se mogu prekinuti ne samo prirodno, već i nenormalno. To se općenito ne preporučuje. Za više informacija pogledajte Prekidanje i prekidanje tokova.

Osnovne .NET funkcije povezane s upotrebom programskih niti koncentrirane su u prostoru imena Threading. Stoga bi većina programa s više niti trebala početi sa sljedećom linijom:

Uvozi System.Threading

Uvoz prostora imena olakšava upisivanje vašeg programa i omogućava IntelliSense tehnologiju.

Direktna povezanost tokova sa procedurama sugerira da na ovoj slici, delegati(vidi poglavlje 6). Konkretno, prostor imena Threading uključuje delegata ThreadStart, koji se obično koristi pri pokretanju softverskih niti. Sintaksa za korištenje ovog delegata izgleda ovako:

Sub ThreadStart javnog delegata ()

Kôd pozvan s delegatom ThreadStart ne smije imati parametre ili povratnu vrijednost, tako da se niti ne mogu kreirati za funkcije (koje vraćaju vrijednost) i za procedure s parametrima. Da biste prenijeli informacije iz toka, također morate potražiti alternativna sredstva, jer izvedene metode ne vraćaju vrijednosti i ne mogu koristiti prijenos po referenci. Na primjer, ako je ThreadMethod u klasi WilluseThread, tada ThreadMethod može komunicirati informacije mijenjanjem svojstava instanci klase WillUseThread.

Domeni aplikacija

.NET niti se izvode u takozvanim domenima aplikacija, koji su u dokumentaciji definisani kao "sandbox u kojem se aplikacija izvodi." Domen aplikacije može se smatrati laganom verzijom Win32 procesa; jedan Win32 proces može sadržavati više domena aplikacija. Glavna razlika između aplikacijskih domena i procesa je u tome što Win32 proces ima svoj vlastiti adresni prostor (u dokumentaciji se domene aplikacija također uspoređuju s logičkim procesima koji se izvode unutar fizičkog procesa). U NET -u se svim upravljanjem memorijom runtime upravlja, pa se više domena aplikacija može izvoditi u jednom Win32 procesu. Jedna od prednosti ove sheme je poboljšana mogućnost skaliranja aplikacija. Alati za rad s aplikacijskim domenama su u klasi AppDomain. Preporučujemo da proučite dokumentaciju za ovu klasu. Uz njegovu pomoć možete dobiti informacije o okruženju u kojem se vaš program izvodi. Konkretno, klasa AppDomain se koristi za obavljanje refleksije o .NET sistemskim klasama. Sljedeći program prikazuje učitane sklopove.

Uvozi sistem.Refleksija

Modul Modulel

Sub Main ()

Zatamnite domenu kao AppDomain

theDomain = AppDomain.CurrentDomain

Dim Assemblies () As

Skupovi = theDomain.GetAssemblies

Dim anAssemblyxAs

Za svaki sklop u sklopovima

Console.WriteLinetanAssembly.Full Name) Dalje

Console.ReadLine ()

End Sub

Završni modul

Kreiranje streamova

Počnimo s rudimentarnim primjerom. Recimo da želite pokrenuti proceduru u zasebnoj niti koja smanjuje vrijednost brojača u beskonačnoj petlji. Postupak je definiran kao dio klase:

Javna klasa WillUseThreads

Javni oduzimač odCounter ()

Zatamni broj kao cijeli broj

Do While True brojanje - = 1

Console.WriteLlne ("Ja sam u drugoj temi i brojač ="

& broji)

Petlja

End Sub

End Class

Budući da je uvjet Do petlje uvijek istinit, mogli biste pomisliti da ništa neće ometati proceduru SubtractFromCounter. Međutim, u višenavojnoj aplikaciji to nije uvijek slučaj.

Sljedeći isječak prikazuje proceduru Sub Main koja pokreće nit i naredbu Imports:

Opcija Strogo pri uvozu System.Threading Module Modulel

Sub Main ()

1 Dim myTest As New WillUseThreads ()

2 Zatamnite bThreadStart kao novi ThreadStart (AddressOf _

myTest.SubtractFromCounter)

3 Zatamnite bThread kao novu nit (bThreadStart)

4 "bThread.Start ()

Dim i As Integer

5 Učinite dok je istina

Console.WriteLine ("U glavnoj niti i broj je" & i) i + = 1

Petlja

End Sub

Završni modul

Pogledajmo redom najvažnije tačke. Prije svega, Sub Man n procedura uvijek funkcionira glavni tok(glavna nit). U .NET programima uvijek rade najmanje dvije niti: glavna nit i nit prikupljanja smeća. Linija 1 stvara novu instancu test klase. U 2. redu stvaramo delegata ThreadStart -a i prosljeđujemo adresu procedure SubtractFromCounter instanci testne klase kreiranoj u 1. redu (ova procedura se poziva bez parametara). DobroUvozom prostora imena Threading, dugo ime se može izostaviti. Novi objekt niti kreira se u retku 3. Primijetite prolaz delegata ThreadStart prilikom pozivanja konstruktora klase Thread. Neki programeri radije spajaju ove dvije linije u jednu logičku liniju:

Zatamni bThread kao novu nit (Nova nitStarttAddressOf _

myTest.SubtractFromCounter))

Konačno, red 4 "pokreće" nit pozivanjem metode Start instance Thread instance kreirane za delegata ThreadStart. Pozivanjem ove metode govorimo operativnom sistemu da bi procedura Oduzimanje trebala biti pokrenuta u zasebnoj niti.

Riječ "počinje" u prethodnom paragrafu je zatvorena pod navodnicima, jer je ovo jedna od mnogih neobičnosti višeslojnog programiranja: pozivanje Start zapravo ne pokreće nit! On samo govori operativnom sistemu da zakaže pokretanje navedene niti, ali je izvan kontrole programa za direktno pokretanje. Nećete moći sami pokrenuti izvršavanje niti, jer operativni sistem uvijek kontrolira izvršavanje niti. U kasnijem odjeljku naučit ćete kako koristiti prioritet kako bi operativni sistem brže pokrenuo vašu nit.

Na sl. 10.1 prikazuje primjer onoga što se može dogoditi nakon pokretanja programa, a zatim ga prekinuti tipkom Ctrl + Break. U našem slučaju, nova nit je započela tek nakon što se brojač u glavnoj niti povećao na 341!

Pirinač. 10.1. Jednostavno vrijeme izvođenja softvera s više niti

Ako program radi duže vrijeme, rezultat će izgledati poput onog prikazanog na Sl. 10.2. Vidimo da si tizavršetak tekuće niti se obustavlja i kontrola se ponovo prebacuje na glavnu nit. U ovom slučaju postoji manifestacija preventivno multitreading kroz vremensko rezanje. Značenje ovog zastrašujućeg izraza objašnjeno je u nastavku.

Pirinač. 10.2. Prebacivanje između niti u jednostavnom višenavojnom programu

Prilikom prekida niti i prenošenja kontrole na druge niti, operativni sistem koristi princip preventivne višestruke niti kroz vremensko rezanje. Kvantiziranje vremena rješava i jedan od uobičajenih problema koji su se ranije javljali u višeslojnim programima - jedna nit zauzima sve vrijeme CPU -a i nije inferiorna u odnosu na kontrolu drugih niti (u pravilu se to događa u intenzivnim ciklusima poput gore navedenog). Da biste spriječili ekskluzivno otimanje CPU -a, vaše niti bi s vremena na vrijeme trebale prenijeti kontrolu na druge niti. Ako se ispostavi da je program "nesvjestan", postoji još jedno, nešto manje poželjno rješenje: operativni sistem uvijek preduhitri pokrenutu nit, bez obzira na njen nivo prioriteta, tako da je pristup procesoru odobren svakoj niti u sistemu.

Budući da sheme kvantizacije svih verzija Windowsa koje pokreću .NET imaju minimalni vremenski odsječak dodijeljen svakoj niti, u .NET programiranju, problemi sa ekskluzivnim hvatanjima CPU -a nisu toliko ozbiljni. S druge strane, ako se .NET okvir ikada prilagodi za druge sisteme, to se može promijeniti.

Ako uključimo sljedeću liniju u naš program prije nego što pozovemo Start, čak će niti s najnižim prioritetom dobiti neki dio CPU vremena:

bThread.Priority = Prioritet niti.Najviši

Pirinač. 10.3. Nit s najvećim prioritetom obično se pokreće brže

Pirinač. 10.4. Procesor je također predviđen za niti nižeg prioriteta

Naredba dodjeljuje maksimalni prioritet novoj niti i smanjuje prioritet glavne niti. Sl. 10.3 može se vidjeti da nova nit počinje raditi brže nego prije, ali, kao što je Sl. 10.4, glavna nit također prima kontrolulijenost (doduše vrlo kratko i tek nakon dužeg rada protoka sa oduzimanjem). Kada pokrenete program na svojim računarima, dobit ćete rezultate slične onima prikazanim na Sl. 10.3 i 10.4, ali zbog razlika između naših sistema neće biti potpunog podudaranja.

Numerirani tip ThreadPrlority uključuje vrijednosti za pet nivoa prioriteta:

ThreadPriority.Highest

ThreadPriority.AboveNormal

ThreadPrlority.Normal

ThreadPriority.BelowNormal

ThreadPriority.Lowest

Metoda pridruživanja

Ponekad se programska nit mora pauzirati dok druga nit ne završi. Recimo da želite pauzirati nit 1 dok nit 2 ne završi svoje izračunavanje. Za ovo iz toka 1 metoda Join se poziva za tok 2. Drugim riječima, naredba

thread2.Join ()

prekida trenutnu nit i čeka da se nit 2. završi. Nit 1 ide na zaključano stanje.

Ako se pridružite toku 1 u toku 2 koristeći metodu pridruživanja, operativni sistem će automatski pokrenuti tok 1 nakon toka 2. Imajte na umu da je proces pokretanja nedeterministički: nemoguće je reći koliko će dugo nakon završetka niti 2 početi raditi nit 1. Postoji još jedna verzija Join -a koja vraća logičku vrijednost:

thread2.Join (Integer)

Ova metoda ili čeka da se nit 2 dovrši, ili deblokira nit 1 nakon isteka navedenog vremenskog intervala, uzrokujući da raspoređivač operativnog sistema ponovo dodijeli vrijeme procesora niti. Metoda vraća True ako se nit 2 završi prije isteka navedenog intervala čekanja, a False u protivnom.

Zapamtite osnovno pravilo: bez obzira je li nit 2 završila ili je isteklo vrijeme, nemate kontrolu nad time kada je nit 1 aktivirana.

Nazivi niti, CurrentThread i ThreadState

Svojstvo Thread.CurrentThread vraća referencu na objekt niti koji se trenutno izvršava.

Iako postoji odličan prozor niti za otklanjanje grešaka u višeslojnim aplikacijama u VB .NET -u, koji je opisan u nastavku, vrlo često nam je pomogla naredba

MsgBox (Thread.CurrentThread.Name)

Često se pokazalo da je kôd izvršen u potpuno različitoj niti od koje je trebao biti izveden.

Podsjetimo se da izraz "nedeterminističko zakazivanje programskih tokova" znači vrlo jednostavnu stvar: programer praktički nema na raspolaganju sredstva da utiče na rad planera. Iz tog razloga, programi često koriste svojstvo ThreadState, koje vraća informacije o trenutnom stanju niti.

Prozor protoka

Prozor Threads u Visual Studiju .NET je neprocjenjiv za otklanjanje grešaka u višenavojnim programima. Aktivira se naredbom Debug> Windows podmeni u režimu prekida. Recimo da ste dodelili ime niti bThread sa sljedećom naredbom:

bThread.Name = "Oduzimanje niti"

Približan prikaz prozora streamova nakon prekida programa kombinacijom tipki Ctrl + Break (ili na neki drugi način) prikazan je na Sl. 10.5.

Pirinač. 10.5. Prozor protoka

Strelica u prvoj koloni označava aktivnu nit koju vraća svojstvo Thread.CurrentThread. Stupac ID sadrži numeričke ID -ove niti. U sljedećoj koloni navedena su imena tokova (ako su dodijeljena). Stupac Location označava proceduru za pokretanje (na primjer, proceduru WriteLine klase Console na slici 10.5). Preostale kolone sadrže informacije o prioritetnim i suspendiranim nitima (pogledajte sljedeći odjeljak).

Prozor niti (ne operativni sistem!) Omogućava vam da kontrolišete niti svog programa pomoću kontekstnih menija. Na primjer, možete zaustaviti trenutnu nit desnim klikom na odgovarajući red i odabirom naredbe Freeze (kasnije se zaustavljena nit može nastaviti). Zaustavljanje niti često se koristi pri otklanjanju pogrešaka kako bi se spriječilo da nit koja ne funkcionira ometa aplikaciju. Osim toga, prozor strimova omogućuje vam aktiviranje drugog (ne zaustavljenog) toka; da biste to učinili, desnom tipkom miša kliknite traženu liniju i odaberite naredbu Prebaci na nit iz kontekstnog izbornika (ili jednostavno dvaput kliknite na liniju niti). Kao što će biti prikazano u nastavku, ovo je vrlo korisno u dijagnosticiranju potencijalnih zastoja.

Obustavljanje prijenosa

Privremeno neiskorišteni tokovi mogu se prenijeti u pasivno stanje pomoću Slajerove metode. Pasivni tok se također smatra blokiranim. Naravno, kada se nit stavi u pasivno stanje, ostatak niti će imati više procesorskih resursa. Standardna sintaksa metode Sleer je sljedeća: Thread.Sleep (interval_in_milliseconds)

Kao rezultat poziva za spavanje, aktivna nit postaje pasivna najmanje određeni broj milisekundi (međutim, aktivacija odmah nakon isteka navedenog intervala nije zajamčena). Napomena: prilikom pozivanja metode referenca na određenu nit se ne prenosi - Sleep metoda se poziva samo za aktivnu nit.

Druga verzija Sleep -a čini da trenutna nit odustane od ostatka dodijeljenog CPU vremena:

Thread.Sleep (0)

Sljedeća opcija stavlja trenutnu nit u pasivno stanje neograničeno vrijeme (aktivacija se događa samo kada pozovete Interrupt):

Thread.Sler (Timeout.Infinite)

Budući da pasivne niti (čak i sa neograničenim vremenskim ograničenjem) mogu biti prekinute metodom Interrupt, što dovodi do pokretanja ThreadlnterruptExcepti u izuzetnim slučajevima, Slayer poziv je uvijek zatvoren u blok Try-Catch, kao u sljedećem isječku:

Pokušajte

Thread.Sleep (200)

"Pasivno stanje niti je prekinuto

Uhvati e kao izuzetak

"Drugi izuzeci

Kraj Pokušaj

Svaki .NET program radi na programskoj niti, pa se metoda mirovanja koristi i za obustavljanje programa (ako program ne uvozi Threadipg imenski prostor, morate koristiti potpuno kvalificirano ime Threading.Thread. Sleep).

Prekidanje ili prekidanje programskih niti

Nit će se automatski prekinuti kada se metoda navede kada se kreira delegat ThreadStart -a, ali ponekad će se metoda (a time i nit) morati prekinuti kada se pojave određeni faktori. U takvim slučajevima tokovi se obično provjeravaju uslovna promenljiva, u zavisnosti od toga u kom stanjudonosi se odluka o hitnom izlasku iz potoka. Obično je Do-While petlja uključena u proceduru za ovo:

Sub ThreadedMethod ()

"Program mora osigurati sredstva za istraživanje

"uslovna varijabla.

"Na primjer, uvjetna varijabla može biti stilizirana kao svojstvo

Učiniti Dok conditionVariable = False And MoreWorkToDo

"Glavni kod

Loop End Sub

Potrebno je neko vrijeme za ispitivanje uvjetne varijable. Uporno prozivanje u stanju petlje trebali biste koristiti samo ako čekate da se nit prerano prekine.

Ako se varijabla stanja mora provjeriti na određenoj lokaciji, upotrijebite naredbu If-Then zajedno s Exit Sub unutar beskonačne petlje.

Pristup uslovnoj varijabli mora biti sinkroniziran tako da izloženost iz drugih niti ne ometa njezinu normalnu upotrebu. Ova važna tema obrađena je u odjeljku "Rješavanje problema: sinkronizacija".

Nažalost, kod pasivnih (ili na drugi način blokiranih) niti se ne izvršava, pa opcija s prozivanjem uvjetne varijable nije prikladna za njih. U ovom slučaju, pozovite metodu prekida na varijabli objekta koja sadrži referencu na željenu nit.

Metoda Interrupt može se pozvati samo na niti u stanju čekanja, mirovanja ili pridruživanja. Ako pozovete Prekini za nit koja se nalazi u jednom od navedenih stanja, tada će nakon nekog vremena nit početi ponovo raditi, a okruženje za izvršavanje će pokrenuti ThreadlnterruptExcepti na izuzetku u niti. To se događa čak i ako je nit neograničeno pasivna pozivanjem Thread.Sleepdimeout. Beskonačno). Kažemo "nakon nekog vremena" jer zakazivanje niti nije determinističko. Iznimka ThreadlnterruptExcepti hvata sekcija Catch koja sadrži izlazni kod iz stanja čekanja. Međutim, odjeljak Catch ne mora prekidati nit na Interrupt pozivu - nit obrađuje iznimku kako joj odgovara.

U .NET -u se metoda Interrupt može pozvati čak i za deblokirane niti. U tom slučaju nit se prekida pri najbližoj blokadi.

Suspendovanje i ubijanje niti

Prostor imena Threading sadrži druge metode koje prekidaju normalno threading:

  • Suspend;
  • Prekini.

Teško je reći zašto je .NET uključivao podršku za ove metode - pozivanje Suspend i Abort vjerojatno će uzrokovati nestabilnost programa. Nijedna od metoda ne dopušta normalnu deinicijalizaciju toka. Osim toga, kada pozovete Suspend ili Abort, ne možete predvidjeti u kakvom će stanju nit ostaviti objekte nakon što je suspendiran ili prekinut.

Pozivanje prekida dovodi do ThreadAbortException. Da bismo vam pomogli da shvatite zašto ovaj čudan izuzetak ne treba rješavati u programima, evo izvatka iz .NET SDK dokumentacije:

“... Kada se nit uništi pozivanjem prekida, vrijeme izvođenja izbacuje ThreadAbortException. Ovo je posebna vrsta izuzetka koju program ne može uhvatiti. Kada se izuzme ovaj izuzetak, vrijeme izvođenja izvršava sve blokove Konačno prije završetka niti. Budući da se svaka radnja može dogoditi u blokovima Konačno, pozovite Pridruži se kako biste bili sigurni da je tok uništen. "

Moral: Prekinuti i Obustaviti se ne preporučuju (a ako i dalje ne možete bez opcije Obustavi, nastavite prekinutu nit pomoću metode Nastavi). Nit možete sigurno prekinuti samo prozivanjem sinhronizirane varijable uvjeta ili pozivanjem gore navedene metode prekida.

Pozadinske niti (demoni)

Neke niti koje se izvode u pozadini automatski prestaju raditi kada prestanu druge programske komponente. Konkretno, sakupljač smeća radi u jednoj od pozadinskih niti. Pozadinske niti se obično stvaraju za primanje podataka, ali to se radi samo ako druge niti pokreću kôd koji može obraditi primljene podatke. Sintaksa: naziv prijenosa IsBackGround = Tačno

Ako su u aplikaciji ostale samo pozadinske niti, aplikacija će se automatski zatvoriti.

Ozbiljniji primjer: izdvajanje podataka iz HTML koda

Preporučujemo korištenje streamova samo ako je funkcionalnost programa jasno podijeljena na nekoliko operacija. Dobar primjer je program za izdvajanje HTML -a u Poglavlju 9. Naša klasa čini dvije stvari: dohvaćanje podataka iz Amazona i njihova obrada. Ovo je savršen primjer situacije u kojoj je višeslojno programiranje zaista prikladno. Kreiramo klase za nekoliko različitih knjiga, a zatim analiziramo podatke u različitim tokovima. Stvaranje nove niti za svaku knjigu povećava učinkovitost programa, jer dok jedna nit prima podatke (što može zahtijevati čekanje na Amazon serveru), druga nit će biti zauzeta obradom već primljenih podataka.

Verzija ovog programa sa više niti funkcioniše efikasnije od verzije sa jednim navojem samo na računaru sa više procesora ili ako se prijem dodatnih podataka može efikasno kombinovati sa njihovom analizom.

Kao što je gore spomenuto, samo se procedure koje nemaju parametre mogu izvoditi u nitima, pa ćete morati napraviti manje promjene u programu. Ispod je osnovni postupak, prepisan kako bi se isključili parametri:

Javni pod FindRank ()

m_Rank = ScrapeAmazon ()

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

End Sub

Budući da nećemo moći koristiti kombinirano polje za spremanje i dohvaćanje informacija (pisanje višenavojnih programa s grafičkim sučeljem razmatra se u posljednjem odjeljku ovog poglavlja), program pohranjuje podatke četiri knjige u niz, čija definicija počinje ovako:

Zatamnite knjigu (3.1) kao niz knjiga (0.0) = "1893115992"

theBook (0.l) = "Programiranje VB .NET" "itd.

Četiri toka stvaraju se u istom ciklusu u kojem se stvaraju objekti AmazonRanker:

Za i = 0 do 3

Pokušajte

theRanker = Novi AmazonRanker (Knjiga (i.0). theBookd.1))

aThreadStart = Novi ThreadStar (AddressOf theRanker.FindRan ()

aThread = Nova nit (aThreadStart)

aThread.Name = Knjiga (i.l)

aThread.Start () Catch e As Exception

Console.WriteLine (e.Message)

Kraj Pokušaj

Sljedeći

Ispod je kompletan tekst programa:

Opcija Strogo pri uvozu System.IO Uvozi System.Net

Uvozi System.Threading

Modul Modulel

Sub Main ()

Zatamnite knjigu (3.1) kao niz

theBook (0.0) = "1893115992"

theBook (0.l) = "Programiranje VB .NET -a"

theBook (l.0) = "1893115291"

theBook (l.l) = "Programiranje baze podataka VB .NET"

theBook (2,0) = "1893115623"

theBook (2.1) = "Uvod programera u C #."

theBook (3.0) = "1893115593"

theBook (3.1) = "Zalijepite .Net platformu"

Dim i As Integer

Zatamnite Ranker As = AmazonRanker

Zatamnite aThreadStart kao Threading.ThreadStart

Zatamnite aThread As Threading.Thread

Za i = 0 do 3

Pokušajte

theRanker = Novi AmazonRankertKnjiga (i.0). knjiga (i.1))

aThreadStart = Novi ThreadStart (Adresa Rankera. FindRanka)

aThread = Nova nit (aThreadStart)

aThread.Name = Knjiga (i.l)

aThread.Start ()

Uhvati e kao izuzetak

Console.WriteLlnete.Message)

Kraj Pokušajte sljedeće

Console.ReadLine ()

End Sub

Završni modul

Javna klasa AmazonRanker

Privatno m_URL As String

Privatni m_Rank kao cijeli broj

Privatno m_naziv kao niz

Javno pod -novo (ByVal ISBN kao niz. ByVal ime kao niz)

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

m_Name = Naziv Kraj podv

Javni podznak FindRank () m_Rank = ScrapeAmazon ()

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

& GetRank) End Sub

Javna svojina samo za čitanje GetRank () As String Get

Ako m_Rank<>0 Onda

Povratak CStr (m_Rank) Inače

"Problemi

Kraj Ako

End Get

End Property

Javno svojstvo samo za čitanje GetName () As String Get

Povratak m_Name

End Get

End Property

Privatna funkcija ScrapeAmazon () Kao cijeli broj Pokušajte

ZatamniteURL kao novi Uri (m_URL)

Zatamnite zahtjev kao WebRequest

theRequest = WebRequest.Create (theURL)

Zatamnite odgovor kao WebResponse

theResponse = theRequest.GetResponse

Zatamni aReader kao novi StreamReader (theResponse.GetResponseStream ())

Zatamnite podatke kao niz

theData = aReader.ReadToEnd

Analiza povratka (podaci)

Uhvati E kao izuzetak

Console.WriteLine (E.Message)

Console.WriteLine (E.StackTrace)

Console. ReadLine ()

Kraj Pokušajte s funkcijom završetka

Analiza privatnih funkcija (ByVal theData As String) As Integer

Dim Location As.Integer Location = theData.IndexOf (" Amazon.com

Prodajni rang:") _

+ "Amazon.com prodajni rang:".Dužina

Dim temp As String

Raditi do theData.Substring (Location.l) = "<" temp = temp

& theData.Substring (Location.l) Lokacija + = 1 petlja

Povrat Clnt (temp)

Završna funkcija

End Class

Višenitne operacije obično se koriste u .NET i I / O imenskim prostorima, pa biblioteka .NET Framework za njih nudi posebne asinhrone metode. Za više informacija o korištenju asinhronih metoda pri pisanju višeslojnih programa pogledajte metode BeginGetResponse i EndGetResponse klase HTTPWebRequest.

Glavna opasnost (opšti podaci)

Do sada se razmatrao jedini siguran način upotrebe niti - naši tokovi nisu promijenili opće podatke. Ako dopustite promjenu općih podataka, potencijalne greške počinju se eksponencijalno umnožavati i postaje ih se znatno teže riješiti za program. S druge strane, ako zabranite izmjenu zajedničkih podataka različitim nitima, višenamjensko .NET programiranje teško će se razlikovati od ograničenih mogućnosti VB6.

Nudimo vam mali program koji demonstrira probleme koji se javljaju bez ulaženja u nepotrebne detalje. Ovaj program simulira kuću sa termostatom u svakoj prostoriji. Ako je temperatura za 5 stepeni celzijusa ili više (oko 2,77 stepeni Celzijusa) niža od cilja, naređujemo sistemu grijanja da poveća temperaturu za 5 stepeni; u suprotnom, temperatura raste samo za 1 stepen. Ako je trenutna temperatura veća ili jednaka podešenoj, ne mijenja se. Regulacija temperature u svakoj prostoriji vrši se zasebnim protokom sa zakašnjenjem od 200 milisekundi. Glavni posao obavlja se sa sljedećim isječkom:

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

Thread.Sleep (200)

Uhvati kravatu kao ThreadlnterruptException

"Pasivno čekanje je prekinuto

Uhvati e kao izuzetak

"Ostale iznimke krajnjih pokušaja

mHouse.HouseTemp + - 5 "itd.

Ispod je kompletan izvorni kod programa. Rezultat je prikazan na Sl. 10.6: Temperatura u kući dosegla je 105 stepeni Celzijusa (40,5 stepeni Celzijusa)!

1 Opcija Strogo uključeno

2 Uvozi System.Threading

3 Modul Modul

4 Sub Main ()

5 Dim myHouse kao nova kuća (l0)

6 Konzola. ReadLine ()

7 End Sub

8 Krajnji modul

9 Public Class House

10 Javni konkurs MAX_TEMP kao cijeli broj = 75

11 Privatno mCurTemp kao cijeli broj = 55

12 privatnih soba mRooms () As Room

13 javnih pod -novih (ByVal numOfRooms kao cijeli broj)

14 ReDim mRooms (numOfRooms = 1)

15 Dim i kao cijeli broj

16 Zatamnite aThreadStart kao Threading.ThreadStart

17 Zatamnite aThread As Thread

18 Za i = 0 Za numOfRooms -1

19 Pokušajte

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

21 aThreadStart - Novi ThreadStart (AddressOf _

mRooms (i) .CheckTempInRoom)

22 aThread = Nova nit (aThreadStart)

23 aThread.Start ()

24 Uhvatite E kao iznimku

25 Console.WriteLine (E.StackTrace)

26 Kraj Pokušaj

27 Dalje

28 End Sub

29 Javna svojina HouseTemp () Kao cijeli broj

trideset. Get

31 Povratak mCurTemp

32 Kraj Get

33 Set (ByVal vrijednost kao cijeli broj)

34 mCurTemp = Vrijednost 35 Kraj skupa

36 Kraj imovine

37 Krajnja klasa

38 Javna učionica

39 Privatni mCurTemp kao cijeli broj

40 Privatno mName As String

41 Privatna mHouse As House

42 Javni pod -novi (ByVal theHouse As House,

ByVal temp As Integer, ByVal roomName As String)

43 mHouse = theHouse

44 mCurTemp = temp

45 mName = naziv sobe

46 End Sub

47 Public Sub CheckTempInRoom ()

48 PromjenaTemperature ()

49 End Sub

50 Temperatura privatne zamjene ()

51 Pokušajte

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

53 Thread.Sleep (200)

54 mHouse.HouseTemp + - 5

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

56 ".Trenutna temperatura je" & mHouse.HouseTemp)

57. Elself mHouse.HouseTemp< mHouse.MAX_TEMP Then

58 Thread.Sleep (200)

59 mHouse.HouseTemp + = 1

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

61 ". Trenutna temperatura je" & mHouse.HouseTemp)

62 Inače

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

64 ".Trenutna temperatura je" & mHouse.HouseTemp)

65 "Ne radite ništa, temperatura je normalna

66 Kraj Ako

67 Uhvati tae kao ThreadlnterruptException

68 "Pasivno čekanje je prekinuto

69 Uhvati e kao iznimku

70 "Drugi izuzeci

71 Kraj pokušaja

72 End Sub

73 Krajnja klasa

Pirinač. 10.6. Problemi sa višestrukim nitima

Postupak Sub Main (redovi 4-7) stvara "kuću" sa deset "soba". Klasa House postavlja maksimalnu temperaturu od 75 stepeni Fahrenheita (oko 24 stepena Celzijusa). Linije 13-28 definiraju prilično složen konstruktor kuće. Ključ za razumijevanje programa su redovi 18-27. Redak 20 stvara još jedan sobni objekt, a referenca na kućni objekt prenosi se konstruktoru tako da se sobni objekt može pozivati ​​na njega ako je potrebno. Linije 21-23 pokreću deset tokova za podešavanje temperature u svakoj prostoriji. Klasa soba definirana je u redovima 38-73. House coxpa referencaje pohranjena u varijabli mHouse u konstruktoru klase Room (linija 43). Kôd za provjeru i podešavanje temperature (redovi 50-66) izgleda jednostavno i prirodno, ali kao što ćete uskoro vidjeti, ovaj utisak vara! Imajte na umu da je ovaj kôd umotan u blok Try-Catch jer program koristi metodu mirovanja.

Teško da bi se neko složio da živi na temperaturama od 105 do 40 stepeni Celzijusa (40,5 do 24 stepena Celzijusa). Šta se desilo? Problem se odnosi na sljedeću liniju:

Ako mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then

I događa se sljedeće: prvo se temperatura provjerava protokom 1. On vidi da je temperatura preniska i podiže je za 5 stepeni. Nažalost, prije nego što temperatura poraste, tok 1 se prekida i kontrola se prenosi na tok 2. Struja 2 provjerava istu varijablu koja još nije promijenjen tok 1. Dakle, protok 2 se također priprema za podizanje temperature za 5 stepeni, ali nema vremena za to i također prelazi u stanje čekanja. Proces se nastavlja sve dok se tok 1 ne aktivira i pređe na sljedeću naredbu - povećanje temperature za 5 stupnjeva. Povećanje se ponavlja kada se aktivira svih 10 tokova, a stanovnici kuće će se loše provesti.

Rešenje problema: sinhronizacija

U prethodnom programu dolazi do situacije kada izlaz programa ovisi o redoslijedu izvođenja niti. Da biste ga se riješili, morate se pobrinuti da naredbe poput

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

aktivna nit u potpunosti obrađuje prije nego što se prekine. Ovo svojstvo se naziva atomska sramota - blok koda mora izvršavati svaka nit bez prekida, kao atomska jedinica. Raspored niti ne može prekinuti grupu naredbi, kombiniranih u atomski blok, sve dok se ne dovrši. Svaki višenamjenski programski jezik ima svoje načine osiguravanja atomskosti. U VB .NET -u, najjednostavniji način korištenja naredbe SyncLock je prosljeđivanje objektne varijable kada se pozove. Napravite male izmjene u proceduri ChangeTemperature iz prethodnog primjera i program će raditi dobro:

Private Sub ChangeTemperature () SyncLock (mHouse)

Pokušajte

Ako mHouse.HouseTemp< mHouse.MAXJTEMP -5 Then

Thread.Sleep (200)

mHouse.HouseTemp + = 5

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

". Trenutna temperatura je" & mHouse.HouseTemp)

Elself

mHouse.HouseTemp< mHouse. MAX_TEMP Then

Thread.Sleep (200) mHouse.HouseTemp + = 1

Console.WriteLine ("Am in" & Me.mName & _ ". Trenutna temperatura je" & mHouse.HomeTemp) Inače

Console.WriteLineC "Am in" & Me.mName & _ ". Trenutna temperatura je" & mHouse.HouseTemp)

"Ne radite ništa, temperatura je normalna

Završi ako uhvati vezu kao ThreadlnterruptException

"Pasivno čekanje prekinulo je Catch e As Exception

"Drugi izuzeci

Kraj Pokušaj

Završite SyncLock

End Sub

Blok kod SyncLock se izvršava atomski. Pristup svim ostalim nitima bit će zatvoren sve dok prva nit ne otpusti zaključavanje naredbom End SyncLock. Ako nit u sinkroniziranom bloku pređe u stanje pasivnog čekanja, zaključavanje ostaje sve dok se nit ne prekine ili nastavi.

Pravilna upotreba naredbe SyncLock održava nit vašeg programa sigurnom. Nažalost, prekomjerna upotreba SyncLocka ima negativan utjecaj na performanse. Sinhronizacija koda u višenavojnom programu smanjuje brzinu njegovog rada nekoliko puta. Sinhronizirajte samo najpotrebniji kôd i otključajte bravu što je prije moguće.

Osnovne klase zbirke nisu sigurne u višenavojnim aplikacijama, ali .NET Framework uključuje verzije većine klasa zbirki koje su sigurne za niti. U ovim klasama, kôd potencijalno opasnih metoda zatvoren je u SyncLock blokove. Verzije klasa zbirki koje su sigurne u nitima trebale bi se koristiti u višežitnim programima gdje god je integritet podataka ugrožen.

Ostaje napomenuti da se uvjetne varijable lako implementiraju pomoću naredbe SyncLock. Da biste to učinili, trebate samo sinhronizirati pisanje sa zajedničkim logičkim svojstvom, dostupnim za čitanje i pisanje, kao što se to radi u sljedećem fragmentu:

Public Class ConditionVariable

Privatni dijeljeni ormarić kao objekt = novi objekt ()

Privatno dijeljeno mOK Kao Booleovo dijeljenje

Svojstvo TheConditionVariable () Kao Booleov

Get

Povratak mOK

End Get

Postavi (ByVal vrijednost kao logičko) SyncLock (ormarić)

mOK = Vrijednost

Završite SyncLock

End Set

End Property

End Class

SyncLock klasa komande i monitora

Korištenje naredbe SyncLock uključuje neke suptilnosti koje nisu prikazane u gornjim jednostavnim primjerima. Dakle, izbor objekta za sinkronizaciju igra vrlo važnu ulogu. Pokušajte pokrenuti prethodni program pomoću naredbe SyncLock (Ja) umjesto SyncLock (mHouse). Temperatura ponovo raste iznad praga!

Upamtite da se naredba SyncLock sinkronizira pomoću objekat, proslijeđen kao parametar, a ne pomoću isječka koda. Parametar SyncLock djeluje kao vrata za pristup sinkroniziranom fragmentu iz drugih niti. Naredba SyncLock (Ja) zapravo otvara nekoliko različitih "vrata", što ste upravo pokušavali izbjeći sinhronizacijom. Moral:

Da bi se zaštitili dijeljeni podaci u višenavojnoj aplikaciji, naredba SyncLock mora sinkronizirati jedan po jedan objekt.

Budući da je sinkronizacija povezana s određenim objektom, u nekim je situacijama moguće nenamjerno zaključati druge fragmente. Recimo da imate dvije sinhronizirane metode, prvu i drugu, a obje metode su sinkronizirane na objektu bigLock. Kada nit 1 prva uđe u metodu i zauzme bigLock, nijedna nit neće moći unijeti drugu metodu jer je pristup njoj već ograničen na nit 1!

Funkcionalnost naredbe SyncLock može se smatrati podskupom funkcionalnosti klase Monitor. Klasa Monitor je vrlo prilagodljiva i može se koristiti za rješavanje netrivijalnih zadataka sinkronizacije. Naredba SyncLock približan je analog metoda Enter i Exi t klase Monitor:

Pokušajte

Monitor.Enter (theObject) Konačno

Monitor.Exit (theObject)

Kraj Pokušaj

Za neke standardne operacije (povećanje / smanjenje varijable, razmjena sadržaja dviju varijabli), .NET Framework pruža Interlocked klasu, čije metode izvode te operacije na atomskoj razini. Koristeći klasu Interlocked, ove operacije su mnogo brže nego upotreba naredbe SyncLock.

Isključeno

Tokom sinhronizacije zaključavanje je postavljeno na objekte, a ne na niti, pa prilikom upotrebe drugačiji objekte za blokiranje drugačiji isječci koda u programima ponekad se dogode sasvim netrivijalne greške. Nažalost, u mnogim slučajevima sinkronizacija na jednom objektu jednostavno je neprihvatljiva jer će prečesto blokirati niti.

Razmotrite situaciju međusobno zaključavanje(zastoj) u svom najjednostavnijem obliku. Zamislite dva programera za stolom. Nažalost, imaju samo jedan nož i jednu vilicu za dvoje. Pod pretpostavkom da su vam za jelo potrebni i nož i vilica, moguće su dvije situacije:

  • Jedan programer uspijeva zgrabiti nož i vilicu i počinje jesti. Kad se zasiti, odlaže večeru sa strane, a zatim ih može uzeti drugi programer.
  • Jedan programer uzima nož, a drugi viljušku. Nitko ne može početi jesti ako se drugi ne odrekne svog aparata.

U višenavojnom programu ova se situacija naziva međusobno blokiranje. Dvije metode su sinkronizirane na različitim objektima. Nit A hvata objekt 1 i ulazi u programski dio zaštićen ovim objektom. Nažalost, da bi funkcionirao, potreban mu je pristup kodu zaštićenom drugom Sync Lock sa drugim objektom za sinhronizaciju. Ali prije nego što ima vremena za unos fragmenta koji je sinkroniziran s drugim objektom, tok B ulazi u njega i hvata ovaj objekt. Sada nit A ne može ući u drugi fragment, nit B ne može ući u prvi fragment, a obje niti su osuđene na neograničeno čekanje. Nijedna nit se ne može nastaviti izvoditi jer potrebni objekt nikada neće biti oslobođen.

Dijagnoza zastoja otežana je činjenicom da se mogu pojaviti u relativno rijetkim slučajevima. Sve ovisi o redoslijedu kojim im raspoređivač dodjeljuje CPU vrijeme. Moguće je da će u većini slučajeva objekti sinkronizacije biti uhvaćeni po slijepom rasporedu.

Slijedi implementacija upravo opisane situacije zastoja. Nakon kratke rasprave o najosnovnijim točkama, pokazat ćemo kako prepoznati zastoj u prozoru niti:

1 Opcija Strogo uključeno

2 Uvozi System.Threading

3 Modul Modul

4 Sub Main ()

5 Dim Tom kao novi programer ("Tom")

6 Dim Bob kao novi programer ("Bob")

7 Zatamnite aThreadStart kao novi ThreadStart (AddressOf Tom.Eat)

8 Zatamnite aThread kao novu nit (aThreadStart)

9 aThread.Name = "Tom"

10 Zatamnite bThreadStart kao novu ThreadStarttAddressOb.Eat)

11 Zatamnite bThread kao novu nit (bThreadStart)

12 bThread.Name = "Bob"

13 aThread.Start ()

14 bThread.Start ()

15 End Sub

16 Krajnji modul

17 Vilica javne klase

18 Privatno dijeljena mForkAvaiTable kao Boolean = Tačno

19 Private Shared mOwner As String = "Nitko"

20 Privatna svojina samo za čitanje OwnsUtensil () As String

21 Get

22 Vrati vlasnika

23 Kraj Get

24 Kraj imovine

25 Javni podgrabForktByVal a kao programer)

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

"pokušavam zgrabiti vilicu.")

27 Console.WriteLine (Me.OwnsUtensil & "ima vilicu."). ...

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

29 Ako je mForkAvailable Tada

30 a.HasFork = Tačno

31 m Vlasnik = a.Moje ime

32 mForkDostupno = Netačno

33 Console.WriteLine (a.MyName & "upravo sam dobio fork.waiting")

34 Pokušajte

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

Kraj Pokušaj

35 Kraj Ako

36 Monitor.Exit (ja)

Završite SyncLock

37 End Sub

38 Krajnja klasa

39 Nož javne klase

40 Privatno deljeni mKnifeDostupno kao Boolean = Tačno

41 Private Shared mOwner As String = "Nitko"

42 Vlasništvo privatne imovine samo za čitanjeUtensi1 () As String

43 Get

44 Povratak vlasnika

45 Kraj Get

46 Završna nekretnina

47 Javni sub GrabKnifetByVal a kao programer)

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

"pokušavam zgrabiti nož.")

49 Console.WriteLine (Me.OwnsUtensil & "ima nož.")

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

51 Ako je mKnifeAvailable Tada

52 mKnifeAvailable = Netačno

53 a.HasKnife = Tačno

54 m Vlasnik = a.Moje ime

55 Console.WriteLine (a.MyName & "upravo sam dobio nož. Čeka")

56 Pokušajte

Thread.Sleep (100)

Uhvati e kao izuzetak

Console.WriteLine (e.StackTrace)

Kraj Pokušaj

57 Kraj Ako

58 Monitor.Exit (ja)

59 End Sub

60 Krajnja klasa

61 Programer javne klase

62 Privatno mName As String

63 Privatno dijeljeno mFork As Fork

64 Privatni dijeljeni mKnife kao nož

65 Privatni mHasKnife kao Boolean

66 Privatno mHasFork kao Booleov

67 Podijeljeno pod -novo ()

68 mFork = Nova vilica ()

69 mKnife = Novi nož ()

70 End Sub

71 Javni pod -novi (ByVal theName As String)

72 mName = ime

73 End Sub

74 Javno svojstvo Samo za čitanje MyName () As String

75 Get

76 Povratak mName

77 Kraj Get

78 Kraj imovine

79 Javna svojina HasKnife () Kao Booleov

80 Get

81 Povratak mHasKnife

82 Kraj Get

83 Skup (ByVal vrijednost kao logička vrijednost)

84 mHasKnife = Vrijednost

85 Krajnji set

86 Kraj imovine

87 Javna svojina HasFork () Kao Booleov

88 Get

89 Povratak mHasFork

90 Kraj Get

91 skup (ByVal vrijednost kao logička vrijednost)

92 mHasFork = Vrijednost

93 Krajnji set

94 Završna nekretnina

95 Javni podjed (()

96 Uradi do mene.HasKnife i ja.HasFork

97 Console.Writeline (Thread.CurrentThread.Name & "je u niti.")

98 Ako je Rnd ()< 0.5 Then

99 mFork.GrabFork (ja)

100 Inače

101 mKnife.GrabKnife (ja)

102 Kraj Ako

103 Petlja

104 MsgBox (Me.MojeIme i "mogu jesti!")

105 mKnife = Novi nož ()

106 mFork = Nova vilica ()

107 End Sub

108 Krajnja klasa

Glavna procedura Main (redovi 4-16) stvara dvije instance klase Programmer, a zatim pokreće dvije niti za izvršavanje kritične metode Eat klase Programmer (linije 95-108), opisane u nastavku. Glavni postupak postavlja imena niti i postavlja ih; vjerovatno je sve što se događa razumljivo i bez komentara.

Kod klase Fork izgleda zanimljivije (redovi 17-38) (slična klasa noža definirana je u redovima 39-60). Redci 18 i 19 navode vrijednosti zajedničkih polja, pomoću kojih možete saznati je li utikač trenutno dostupan, a ako ne, ko ga koristi. Svojstvo ReadOnly OwnUtensi1 (redovi 20-24) namijenjeno je za najjednostavniji prijenos informacija. U središtu klase Fork je GrabFork metoda "grab the fork", definirana u redovima 25-27.

  1. Redci 26 i 27 jednostavno ispisuju informacije o otklanjanju grešaka na konzoli. U glavnom kodu metode (redovi 28-36) pristup viljušci je sinhroniziran objektompojas Me. Budući da naš program koristi samo jednu vilicu, Me sync osigurava da je dvije niti ne mogu uhvatiti istovremeno. Naredba Slee "p (u bloku koji počinje u retku 34) simulira kašnjenje između hvatanja vilice / noža i početka jela. Imajte na umu da naredba Sleep ne otključava objekte i samo ubrzava zastoj!
    Međutim, najzanimljiviji je kôd klase Programer (redovi 61-108). Redovi 67-70 definiraju generički konstruktor koji osigurava da u programu postoji samo jedna vilica i nož. Šifra svojstva (redovi 74-94) je jednostavna i ne zahtijeva komentar. Najvažnija stvar se događa u metodi Eat, koju izvode dvije zasebne niti. Proces se nastavlja u petlji sve dok neki mlaz ne zahvati vilicu zajedno s nožem. Na linijama 98-102, objekt nasumično hvata vilicu / nož pomoću poziva Rnd, što uzrokuje zastoj. Događa se sledeće:
    Nit koji izvršava metodu Eat Thothovog objekta se poziva i ulazi u petlju. Zgrabi nož i pređe u stanje čekanja.
  2. Nit koji izvršava Bob's Eat metodu se poziva i ulazi u petlju. Ne može zgrabiti nož, ali zgrabi vilicu i prelazi u stanje pripravnosti.
  3. Nit koji izvršava metodu Eat Thothovog objekta se poziva i ulazi u petlju. Pokušava zgrabiti vilicu, ali Bob je već zgrabio viljušku; nit prelazi u stanje čekanja.
  4. Nit koji izvršava Bob's Eat metodu se poziva i ulazi u petlju. Pokušava zgrabiti nož, no nož ga je već uhvatio objekt Thoth; nit prelazi u stanje čekanja.

Sve se to nastavlja u nedogled - suočeni smo s tipičnom situacijom zastoja (pokušajte pokrenuti program i vidjet ćete da nitko ne može jesti na ovaj način).
Također možete provjeriti je li došlo do zastoja u prozoru niti. Pokrenite program i prekinite ga tipkama Ctrl + Break. Uključite varijablu Me u okvir za prikaz i otvorite prozor streamova. Rezultat izgleda nešto poput onog prikazanog na Sl. 10.7. Iz slike se vidi da je Bobov konac uhvatio nož, ali nema vilicu. Desnom tipkom miša kliknite u prozoru Threads na liniji Tot i odaberite naredbu Switch to Thread iz kontekstnog izbornika. Iz prikaza se vidi da tok Thoth ima vilicu, ali bez noža. Naravno, ovo nije stopostotni dokaz, ali takvo ponašanje vas barem tjera da posumnjate da nešto nije u redu.
Ako opcija sa sinkronizacijom jednim objektom (kao u programu s povećanjem -temperature u kući) nije moguća, za sprječavanje međusobnih zaključavanja možete numerirati objekte za sinkronizaciju i uvijek ih hvatati u stalnom redoslijedu. Nastavimo analogiju programera za ručavanje: ako konac uvijek prvo uzima nož, a zatim viljušku, neće biti problema sa blokiranjem. Prvi mlaz koji zgrabi nož moći će normalno jesti. Prevedeno na jezik programskih tokova, to znači da je hvatanje objekta 2 moguće samo ako se objekt 1 prvo uhvati.

Pirinač. 10.7. Analiza zastoja u prozoru niti

Stoga, ako uklonimo poziv Rnd -u na liniji 98 i zamijenimo ga isječkom

mFork.GrabFork (ja)

mKnife.GrabKnife (ja)

zastoj nestaje!

Surađujte na podacima dok se stvaraju

U višenavojnim aplikacijama često se javlja situacija u kojoj niti ne rade samo sa zajedničkim podacima, već i čekaju da se pojave (to jest, nit 1 mora stvoriti podatke prije nego što ih nit 2 može koristiti). Budući da se podaci dijele, pristup tim podacima mora biti sinhroniziran. Također je potrebno osigurati sredstva za obavještavanje niti čekanja o pojavi spremnih podataka.

Ova situacija se obično naziva problem dobavljača / potrošača. Nit pokušava pristupiti podacima koji još ne postoje, pa mora prenijeti kontrolu na drugu nit koja stvara potrebne podatke. Problem se rješava sljedećim kodom:

  • Nit 1 (potrošač) se budi, ulazi u sinhronizovanu metodu, traži podatke, ne pronalazi ih i prelazi u stanje čekanja. Preliminarnofizički, mora ukloniti blokadu kako ne bi ometao rad opskrbnog konca.
  • Nit 2 (provajder) ulazi u sinhronizovanu metodu oslobođenu od niti 1, stvara podataka za tok 1 i na neki način obavještava tok 1 o prisutnosti podataka. Zatim oslobađa zaključavanje kako bi nit 1 mogla obraditi nove podatke.

Ne pokušavajte riješiti ovaj problem neprestanim pozivanjem niti 1 i provjerom stanja varijable stanja čija je vrijednost> postavljena nizom 2. Ova odluka će ozbiljno utjecati na performanse vašeg programa, jer će se u većini slučajeva nit 1 pozivati ​​na ne razlog; i nit 2 će čekati toliko često da će mu nestati vremena za stvaranje podataka.

Odnosi davatelj / potrošač vrlo su česti, pa se za takve situacije stvaraju posebni primitivi u bibliotekama klasa programiranja s više niti. U NET-u se ti primitivi nazivaju Wait i Pulse-PulseAl 1 i dio su klase Monitor. Slika 10.8 prikazuje situaciju koju ćemo programirati. Program organizira tri reda niti: red čekanja, red blokiranja i red izvršavanja. Planer niti ne dodjeljuje CPU vrijeme nitima koje su u redu čekanja. Da bi niti bilo dodijeljeno vrijeme, mora se premjestiti u red izvršavanja. Kao rezultat toga, rad aplikacije je organiziran mnogo efikasnije nego s uobičajenim ispitivanjem uvjetne varijable.

U pseudokodu, idiom potrošača podataka formuliran je na sljedeći način:

"Ulaz u sinhronizirani blok sljedeće vrste

Iako nema podataka

Idite na red čekanja

Petlja

Ako postoje podaci, obradite ih.

Napustite sinhronizovani blok

Odmah nakon izvršavanja naredbe Wait, nit se suspendira, zaključavanje se otpušta i nit ulazi u red čekanja. Kada se zaključavanje otpusti, nit u redu izvršavanja se može pokrenuti. Vremenom će jedna ili više blokiranih niti stvoriti podatke potrebne za rad niti koja se nalazi u redu čekanja. Budući da se validacija podataka vrši u petlji, prijelaz na korištenje podataka (nakon petlje) događa se samo kada postoje podaci spremni za obradu.

U pseudokodu idiom dobavljača podataka izgleda ovako:

"Ulazak u blok sinhroniziranog prikaza

Iako podaci NISU potrebni

Idite na red čekanja

Inače proizvodi podatke

Kad su podaci spremni, pozovite Pulse-PulseAll.

za premještanje jedne ili više niti iz reda blokiranja u red izvođenja. Napustite sinhronizirani blok (i vratite se u red za pokretanje)

Pretpostavimo da naš program simulira porodicu s jednim roditeljem koji zarađuje novac i djetetom koje troši taj novac. Kad novac prođeispada da dijete mora čekati dolazak novog iznosa. Softverska implementacija ovog modela izgleda ovako:

1 Opcija Strogo uključeno

2 Uvozi System.Threading

3 Modul Modul

4 Sub Main ()

5 Zatamnite porodicu kao novu porodicu ()

6 theFamily.StartltsLife ()

7 End Sub

8 Kraj fjodule

9

10 Porodica javne klase

11 Privatni mMoney kao cijeli broj

12 Privatni mWeek As Integer = 1

13 Javni Pod StartltsLife ()

14 Zatamnite aThreadStart kao novi ThreadStarUAddressMe.Produce)

15 Zatamni bThreadStart kao novi ThreadStarUAddressMe.Consume)

16 Zatamnite aThread kao novu nit (aThreadStart)

17 Zatamnite bThread kao novu nit (bThreadStart)

18 aThread.Name = "Proizvodi"

19 aThread.Start ()

20 bThread.Name = "Potroši"

21 bTit. Start ()

22 End Sub

23 Javno vlasništvo TheWeek () Kao cijeli broj

24 Get

25 Povratak u sedmicu

26 Kraj Get

27 Set (ByVal vrijednost kao cijeli broj)

28 sedmica - Vrijednost

29 Kraj set

30 Kraj imovine

31 Javna svojina OurMoney () Kao cijeli broj

32 Get

33 Povratak mMoney

34 Kraj Get

35 Set (ByVal vrijednost kao cijeli broj)

36 mMoney = Vrijednost

37 Kraj set

38 Kraj imovine

39 Javna podprodukcija ()

40 Thread.Sleep (500)

41 Do

42 Monitor.Enter (ja)

43 Radite dok ja.Naš novac> 0

44 Monitor.Čekajte (ja)

45 Petlja

46 Ja.Naš novac = 1000

47 Monitor.PulseAll (ja)

48 Monitor.Exit (ja)

49 Petlja

50 End Sub

51 Javna podkonzuma ()

52 MsgBox ("Trenutno sam u niti potrošnje")

53 Do

54 Monitor.Enter (ja)

55 Radite dok ja.Naš novac = 0

56 Monitor.Čekajte (ja)

57 Petlja

58 Console.WriteLine ("Dragi roditelje, upravo sam potrošio sve tvoje" & _

novac sedmično "& TheWeek)

59 Sedmica + = 1

60 Ako je TheWeek = 21 * 52 Tada System.Environment.Exit (0)

61 Ja.Naš novac = 0

62 Monitor.PulseAll (ja)

63 Monitor.Exit (ja)

64 Petlja

65 End Sub

66 Krajnja klasa

Metoda StartltsLife (redovi 13-22) priprema se za pokretanje tokova Produce and Consume. Najvažnije se događa u tokovima Produce (redovi 39-50) i Consume (redovi 51-65). Postupak subprodukcije provjerava dostupnost novca, a ako novca ima, odlazi u red čekanja. U suprotnom, roditelj generira novac (linija 46) i obavještava objekte u redu čekanja o promjeni situacije. Imajte na umu da poziv Pulse-Pulse All stupa na snagu samo kada se zaključavanje otpusti naredbom Monitor.Exit. Nasuprot tome, postupak Sub Consume provjerava dostupnost novca, a ako nema novca, obavještava o tome budućeg roditelja. Red 60 jednostavno prekida program nakon 21 uvjetne godine; pozivni sistem. Environment.Exit (0) je .NET analog naredbe End (naredba End je takođe podržana, ali za razliku od System. Environment. Exit, ne vraća izlazni kod operativnom sistemu).

Niti koje su stavljene na red čekanja moraju biti oslobođene od drugih dijelova vašeg programa. Iz tog razloga radije koristimo PulseAll umjesto Pulse. Budući da nije unaprijed poznato koja će se nit aktivirati kada se pozove Pulse 1, ako u redu ima relativno malo niti, možete jednako dobro pozvati i PulseAll.

Multithreading u grafičkim programima

Naša rasprava o višestrukom prožimanju u GUI aplikacijama započinje primjerom koji objašnjava čemu služi multitreading u GUI aplikacijama. Napravite obrazac s dva gumba Start (btnStart) i Cancel (btnCancel), kao što je prikazano na Sl. 10.9. Klikom na dugme Start generira se klasa koja sadrži slučajni niz od 10 miliona znakova i metoda za brojanje pojavljivanja slova "E" u tom dugom nizu. Obratite pažnju na upotrebu klase StringBuilder za učinkovitije stvaranje dugih nizova.

Korak 1

Nit 1 primjećuje da za to nema podataka. Poziva Wait, otključava zaključavanje i odlazi u red čekanja.



Korak 2

Kada se brava otpusti, nit 2 ili nit 3 napuštaju red blokova i ulaze u sinkronizirani blok, stječući zaključavanje

Korak 3

Recimo da nit 3 ulazi u sinkronizirani blok, stvara podatke i poziva Pulse-Pulse All.

Odmah nakon što izađe iz bloka i otpusti zaključavanje, nit 1 se premješta u red izvršavanja. Ako nit 3 poziva Pluse, samo jedan ulazi u red izvršavanjanit, kada se pozove Pluse All, sve niti idu u red izvršavanja.



Pirinač. 10.8. Problem dobavljača / potrošača

Pirinač. 10.9. Multithreading u jednostavnoj GUI aplikaciji

Uvozi System.Text

Javna klasa RandomCharacters

Privatni m_Data kao StringBuilder

Privatna mjength, m_count Kao cijeli broj

Javno pod -novo (ByVal n kao cijeli broj)

m_dužina = n -1

m_Data = Novi StringBuilder (m_length) MakeString ()

End Sub

Privatni pod MakeString ()

Dim i As Integer

Zatamni myRnd kao novi slučajni ()

Za i = 0 do m_dužine

"Generirajte slučajni broj između 65 i 90,

"pretvori u velika slova

"i priložite objektu StringBuilder

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

Sljedeći

End Sub

Javni podpočetni broj ()

GetEes ()

End Sub

Privatni Sub GetEes ()

Dim i As Integer

Za i = 0 do m_dužine

Ako je m_Data.Chars (i) = CChar ("E") Tada

m_broj + = 1

Završi ako je sljedeće

m_CountDone = Tačno

End Sub

Javno samo za čitanje

Svojstvo GetCount () Kao cijeli broj Get

Ako nije (m_CountDone) Tada

Povrat m_count

Kraj Ako

Kraj Get Kraj svojstva

Javno samo za čitanje

Svojstvo IsDone () Kao Boolean Get

Povratak

m_CountDone

End Get

End Property

End Class

Vrlo je jednostavan kod povezan s dva dugmeta na obrascu. Postupak btn-Start_Click instancira gornju klasu RandomCharacters, koja inkapsulira niz s 10 miliona znakova:

Privatni Sub btnStart_Click (ByVal pošiljalac Kao System.Object.

ByVal e As System.EventArgs) Rukuje btnSTart.Click

Dim RC kao novi slučajni likovi (10000000)

RC.StartCount ()

MsgBox ("Broj es je" & RC.GetCount)

End Sub

Dugme Otkaži prikazuje okvir za poruku:

Privatni Sub btnCancel_Click (ByVal pošiljalac kao System.Object._

ByVal e As System.EventArgs) Rukuje btnCancel.Click

MsgBox ("Brojanje prekinuto!")

End Sub

Kada se program pokrene i pritisne dugme Start, ispostavlja se da dugme Odustani ne reaguje na unos korisnika jer neprekidna petlja sprečava dugme da upravlja događajem koji primi. To je neprihvatljivo u modernim programima!

Postoje dva moguća rješenja. Prva opcija, dobro poznata iz prethodnih verzija VB -a, odbacuje multithreading: poziv DoEvents uključen je u petlju. U NET -u ova naredba izgleda ovako:

Application.DoEvents ()

U našem primjeru ovo definitivno nije poželjno - ko želi usporiti program sa deset miliona DoEvents poziva! Ako umjesto toga petlju dodijelite posebnoj niti, operativni sistem će se prebacivati ​​između niti, a gumb Odustani će ostati funkcionalan. Implementacija s zasebnom niti prikazana je dolje. Da bismo jasno pokazali da dugme Odustani radi, kada ga pritisnemo, jednostavno prekidamo program.

Sljedeći korak: Prikažite dugme za brojanje

Recimo da ste odlučili pokazati svoju kreativnu maštu i dati obliku izgled prikazan na Sl. 10.9. Napomena: dugme Prikaži broj još nije dostupno.

Pirinač. 10.10. Obrazac zaključanog dugmeta

Očekuje se da će zasebna nit odbrojavati i otključati nedostupno dugme. To se naravno može učiniti; štaviše, takav se zadatak javlja često. Nažalost, nećete moći djelovati na najočitiji način - povežite sekundarnu nit s niti grafičkog sučelja držeći vezu do gumba ShowCount u konstruktoru ili čak koristeći standardnog delegata. Drugim riječima, nikad ne koristite donju opciju (osnovno) pogrešno redovi su podebljani).

Javna klasa RandomCharacters

Privatno m_0ata Kao StringBuilder

Privatno m_CountDone Kao logičko

Privatna mjength. m_count Kao cijeli broj

Privatno m_Button kao Windows.Forms.Button

Javni pod -novi (ByVa1 n kao cijeli broj, _

ByVal b Kao Windows.Forms.Button)

m_dužina = n - 1

m_Data = Novi StringBuilder (mJength)

m_Button = b MakeString ()

End Sub

Privatni pod MakeString ()

Dim I kao cijeli broj

Zatamni myRnd kao novi slučajni ()

Za I = 0 do m_dužine

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

Sljedeći

End Sub

Javni podpočetni broj ()

GetEes ()

End Sub

Privatni Sub GetEes ()

Dim I kao cijeli broj

Za I = 0 Za mjernu dužinu

Ako je m_Data.Chars (I) = CChar ("E") Tada

m_broj + = 1

Završi ako je sljedeće

m_CountDone = Tačno

m_Button.Enabled = Tačno

End Sub

Javno samo za čitanje

Svojstvo GetCount () Kao cijeli broj

Get

Ako nije (m_CountDone) Tada

Bacite novu iznimku ("Brojanje još nije dovršeno") inače

Povrat m_count

Kraj Ako

End Get

End Property

Javno svojstvo samo za čitanje IsDone () Kao Booleovski

Get

Povratak m_CountDone

End Get

End Property

End Class

Vjerojatno će ovaj kôd uspjeti u nekim slučajevima. Ipak:

  • Ne može se organizirati interakcija sekundarne niti sa niti koja stvara GUI očigledno znači.
  • Nikad ne mijenjajte elemente u grafičkim programima iz drugih programskih tokova. Sve promjene trebale bi se dogoditi samo u niti koja je kreirala GUI.

Ako prekršite ova pravila, mi garantujemo da će se suptilne, suptilne greške pojaviti u vašim grafičkim programima sa više niti.

Također neće uspjeti organizirati interakciju objekata pomoću događaja. Radnik sa 06 događaja radi na istoj niti kao i RaiseEvent pa vam događaji neće pomoći.

Ipak, zdrav razum nalaže da grafičke aplikacije moraju imati mogućnost mijenjanja elemenata iz druge niti. U NET Framework-u postoji način siguran za pozivanje metoda GUI aplikacija iz druge niti. U tu svrhu koristi se posebna vrsta delegata pozivača metoda iz prostora imena System.Windows. Obrasci. Sljedeći isječak prikazuje novu verziju metode GetEes (promijenjene linije podebljane):

Privatni Sub GetEes ()

Dim I kao cijeli broj

Za I = 0 do m_dužine

Ako je m_Data.Chars (I) = CChar ("E") Tada

m_broj + = 1

Završi ako je sljedeće

m_CountDone = Istinski pokušaj

Dim mylnvoker kao novi metodlnvoker (AddressOf UpDateButton)

myInvoker.Invoke () Catch e As ThreadlnterruptException

"Neuspjeh

Kraj Pokušaj

End Sub

Javna pod -upDateButton ()

m_Button.Enabled = Tačno

End Sub

Međupozivni pozivi na dugme ne vrše se direktno, već putem metoda Invoker. .NET Framework garantuje da je ova opcija sigurna za niti.

Zašto postoji toliko problema s višeslojnim programiranjem?

Sada kada ste malo razumjeli višežitnost i potencijalne probleme povezane s njom, odlučili smo da bi bilo prikladno odgovoriti na pitanje iz naslova ovog pododjeljka na kraju ovog poglavlja.

Jedan od razloga je i taj što je višestruko nitanje nelinearni proces i navikli smo na model linearnog programiranja. U početku se teško naviknuti na samu ideju da se izvršavanje programa može nasumično prekinuti, a kontrola prenijeti na drugi kod.

Međutim, postoji još jedan, fundamentalniji razlog: ovih dana programeri prerijetko programiraju u asembleru, ili barem gledaju rastavljeni izlaz kompajlera. U suprotnom bi im bilo mnogo lakše naviknuti se na ideju da desetine uputstava za sastavljanje mogu odgovarati jednoj naredbi jezika na visokom nivou (kao što je VB .NET). Nit se može prekinuti nakon bilo koje od ovih uputa, pa stoga usred naredbe na visokom nivou.

Ali to nije sve: moderni kompajleri optimiziraju performanse programa, a računarski hardver može ometati upravljanje memorijom. Kao rezultat toga, kompajler ili hardver mogu bez vašeg znanja promijeniti redoslijed naredbi navedenih u izvornom kodu programa [ Mnogi kompajleri optimiziraju ciklično kopiranje nizova, na primjer za i = 0 do n: b (i) = a (i): ncxt. Prevodilac (ili čak specijalizirani upravitelj memorije) može jednostavno stvoriti niz i zatim ga napuniti jednom operacijom kopiranja umjesto da više puta kopira pojedinačne elemente!].

Nadajmo se da će vam ova objašnjenja pomoći da bolje razumijete zašto višeslojno programiranje uzrokuje toliko problema - ili barem manje iznenađenje čudnim ponašanjem vaših višeslojnih programa!

Primjer izgradnje jednostavne višenavojne aplikacije.

Rođen zbog mnogih pitanja o izgradnji višeslojnih aplikacija u Delphiju.

Svrha ovog primjera je pokazati kako pravilno izgraditi višenavojnu aplikaciju, uzimajući dugotrajan rad u zasebnu nit. I kako u takvoj aplikaciji osigurati interakciju glavne niti s radnikom za prijenos podataka iz obrasca (vizualne komponente) u tok i obrnuto.

Primjer ne tvrdi da je potpun, on samo pokazuje najjednostavnije načine interakcije među nitima. Dopuštajući korisniku da "brzo zaslijepi" (ko bi znao koliko ga mrzim) ispravno funkcionirajuću višedenitnu aplikaciju.
Sve u njemu je detaljno komentirano (po mom mišljenju), ali ako imate pitanja, pitajte.
Ali još jednom vas upozoravam: Streamovi nisu laki... Ako nemate pojma kako sve to funkcionira, postoji velika opasnost da vam često sve funkcionira kako treba, a ponekad će se i program ponašati više nego čudno. Ponašanje nepravilno napisanog višeslojnog programa uvelike ovisi o velikom broju faktora koji se ponekad ne mogu reproducirati tijekom otklanjanja pogrešaka.

Pa primjer. Radi praktičnosti, postavio sam i kôd i priložio arhivu sa kodom modula i obrasca

jedinica ExThreadForm;

koristi
Windows, poruke, sistemske aplikacije, varijante, klase, grafike, kontrole, obrasci,
Dijalozi, StdCtrls;

// konstante koje se koriste pri prijenosu podataka iz toka u obrazac pomoću
// šalje prozor poruke
const
WM_USER_SendMessageMetod = WM_USER + 10;
WM_USER_PostMessageMetod = WM_USER + 11;

tip
// opis klase niti, potomak tThread -a
tMyThread = class (tThread)
privatno
SyncDataN: Integer;
SyncDataS: String;
procedura SyncMetod1;
zaštićen
procedure Execute; override;
javnosti
Param1: niz;
Param2: cijeli broj;
Param3: Boolean;
Zaustavljeno: Boolean;
LastRandom: Integer;
IterationNo: Integer;
ResultList: tStringList;

Konstruktor Kreiraj (aParam1: String);
destructor Destroy; override;
end;

// opis klase oblika pomoću toka
TForm1 = klasa (TForm)
Label1: TLabel;
Zapis1: TMemo;
btnStart: TButton;
btnStop: TButton;
Edit1: TEdit;
Edit2: TEdit;
CheckBox1: TCheckBox;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
procedura btnStartClick (Pošiljalac: TObject);
procedura btnStopClick (Pošiljalac: TObject);
privatno
(Privatne izjave)
MyThread: tMyThread;
procedura EventMyThreadOnTerminate (Pošiljalac: tObject);
procedura EventOnSendMessageMetod (var Msg: TMessage); poruka WM_USER_SendMessageMetod;
procedura EventOnPostMessageMetod (var Msg: TMessage); poruka WM_USER_PostMessageMetod;

Javno
(Javne izjave)
end;

var
Form1: TForm1;

{
Zaustavljeno - Pokazuje prijenos podataka iz obrasca u tok.
Dodatna sinhronizacija nije potrebna jer je jednostavna
tipa od jedne riječi, a piše samo jedna nit.
}

procedura TForm1.btnStartClick (Pošiljalac: TObject);
početi
Randomize (); // osiguravanje slučajnosti u nizu pomoću Random () - nema nikakve veze sa tokom

// Kreirajte instancu stream objekta, prosljeđujući mu ulazni parametar
{
PAŽNJA!
Konstruktor toka je napisan na takav način da je tok kreiran
suspendovana jer dozvoljava:
1. Kontrolišite trenutak lansiranja. To je gotovo uvijek prikladnije jer
omogućuje vam postavljanje prijenosa čak i prije početka, proslijedite ga na unos
parametri itd.
2. Zato što tada će se veza do kreiranog objekta spremiti u polje obrasca
nakon samouništenja niti (vidi dolje) koje kada nit radi
može doći u bilo koje vrijeme, ova veza će postati nevažeća.
}
MyThread: = tMyThread.Create (Form1.Edit1.Text);

// Međutim, budući da je nit stvorena obustavljena, onda na bilo kakve greške
// za vrijeme njegove inicijalizacije (prije početka) moramo ga sami uništiti
// za ono što koristimo try / except block
probaj

// Dodjeljivanje rukovatelja prekidanja niti u kojem ćemo primati
// rezultate rada streama i "prebrisati" vezu do njega
MyThread.OnTerminate: = EventMyThreadOnTerminate;

// Budući da će se rezultati prikupljati u OnTerminate, tj. prije samouništenja
// tok ćemo ukloniti brige oko njegovog uništenja
MyThread.FreeOnTerminate: = Tačno;

// Primjer prolaska ulaznih parametara kroz polja stream objekta, u točki
// instancirati kada već nije pokrenut.
// Osobno, radije to radim kroz parametre nadjačanog
// konstruktor (tMyThread.Create)
MyThread.Param2: = StrToInt (Form1.Edit2.Text);

MyThread.Stopped: = False; // i svojevrsni parametar, ali se mijenja
// vrijeme izvođenja niti
osim
// budući da nit još nije pokrenuta i neće se moći samouništiti, uništit ćemo je "ručno"
FreeAndNil (MyThread);
// i tada dopustite da se iznimka obradi na uobičajen način
podići;
end;

// Budući da je objekt niti uspješno kreiran i konfiguriran, vrijeme je da ga pokrenete
MyThread.Resume;

ShowMessage ("Stream pokrenut");
end;

procedura TForm1.btnStopClick (Pošiljalac: TObject);
početi
// Ako instanca niti još uvijek postoji, zatražite da se zaustavi
// I, točno "pitaj". U principu, možemo i "prisiliti", ali hoće
// izuzetno hitna opcija, koja zahtijeva jasno razumijevanje svega ovoga
// streaming kuhinja. Stoga se ovdje ne razmatra.
ako je dodijeljeno (MyThread) tada
MyThread.Stopped: = Tačno
else
ShowMessage ("Nit se ne pokreće!");
end;

procedura TForm1.EventOnSendMessageMetod (var Msg: TMessage);
početi
// metoda za obradu sinhrone poruke
// u WParam -u adresa objekta tMyThread, u LParam -u trenutna vrijednost LastRandom niti
sa tMyThread (Msg.WParam) počnite
Form1.Label3.Caption: = Format ("% d% d% d",);
end;
end;

procedura TForm1.EventOnPostMessageMetod (var Msg: TMessage);
početi
// metoda za rukovanje asinhronom porukom
// u WParam -u trenutna vrijednost IterationNo, u LParam -u trenutna vrijednost toka LastRandom
Form1.Label4.Caption: = Format ("% d% d",);
end;

procedura TForm1.EventMyThreadOnTerminate (Pošiljalac: tObject);
početi
// BITAN!
// Metoda za rukovanje događajem OnTerminate uvijek se poziva u kontekstu glavnog
// thread - ovo je zagarantovano implementacijom tThread. Stoga u njemu možete slobodno
// koristiti bilo koja svojstva i metode bilo kojih objekata

// Za svaki slučaj provjerite da li instanca objekta još uvijek postoji
ako nije dodijeljeno (MyThread) tada izađite; // ako ga nema, nema šta raditi

// dobivamo rezultate rada niti instance instance objekta niti
Form1.Memo1.Lines.Add (Format ("Tok je završio rezultatom% d",));
Form1.Memo1.Lines.AddStrings ((Pošiljalac kao tMyThread) .ResultList);

// Uništiti referencu na instancu stream objekta.
// Budući da se naša nit samouništava (FreeOnTerminate: = True)
// nakon što se završi rukovatelj OnTerminate, instanca objekta toka bit će
// je uništen (besplatno) i sve reference na njega postat će nevažeće.
// Kako ne bismo slučajno naišli na takvu vezu, zaglavit ćemo MyThread
// Još jednom ću primijetiti - nećemo uništiti objekt, već samo prebrisati vezu. Objekat
// uništiti sebe!
Moja tema: = Ništa;
end;

konstruktor tMyThread.Create (aParam1: String);
početi
// Kreirajte instancu SUSPENDED toka (pogledajte komentar prilikom instanciranja)
naslijeđeno Create (True);

// Kreiranje unutrašnjih objekata (ako je potrebno)
ResultList: = tStringList.Create;

// Dobivanje početnih podataka.

// Kopiramo ulazne podatke proslijeđene kroz parametar
Param1: = aParam1;

// Primjer primanja ulaznih podataka iz VCL komponenti u konstruktoru stream objekta
// Ovo je prihvatljivo u ovom slučaju, budući da se konstruktor poziva u kontekstu
// glavna nit. Stoga se VCL komponentama može pristupiti ovdje.
// Ali, ovo mi se ne sviđa, jer mislim da je loše kad nit nešto zna
// o nekom obliku tamo. Ali, šta ne možete učiniti za demonstraciju.
Param3: = Form1.CheckBox1.Checked;
end;

destructor tMyThread.Destroy;
početi
// uništavanje unutrašnjih objekata
FreeAndNil (ResultList);
// uništava bazu tThread
naslijeđen;
end;

procedura tMyThread.Execute;
var
t: Kardinal;
s: String;
početi
IterationNo: = 0; // brojač rezultata (broj ciklusa)

// U mom primjeru tijelo niti je petlja koja završava
// ili vanjskim "zahtjevom" za prekidom prošao kroz parametar varijable Zaustavljeno,
// ili samo radeći 5 petlji
// Ugodnije mi je ovo napisati kroz "vječnu" petlju.

Dok True počinje

Inc (broj ponavljanja); // broj sljedećeg ciklusa

LastRandom: = Random (1000); // broj ključa - za prikaz prijenosa parametara iz toka u obrazac

T: = Slučajno (5) +1; // vrijeme za koje ćemo zaspati ako nismo dovršeni

// Glup rad (ovisno o ulaznom parametru)
ako ne Param3 onda
Inc (Param2)
else
Decembar (Param2);

// Formiranje posrednog rezultata
s: = Format ("% s% 5d% s% d% d",
);

// Dodajte među rezultat na listu rezultata
ResultList.Add (s);

//// Primjeri prosljeđivanja posrednog rezultata u obrazac

//// Prolazak kroz sinhronizovanu metodu - klasičan način
//// Nedostaci:
//// - metoda koja se sinkronizira obično je metoda klase toka (za pristup
//// u polja stream objekta), ali, za pristup poljima obrasca, mora
//// "znati" o njemu i njegovim poljima (objektima), što obično nije dobro
//// tačka gledišta organizacije programa.
//// - trenutna nit će biti obustavljena dok se izvršavanje ne završi
//// sinhronizovana metoda.

//// Prednosti:
//// - standardno i svestrano
//// - u sinhronizovanoj metodi možete koristiti
//// sva polja stream objekta.
// prvo, ako je potrebno, trebate spremiti prenesene podatke u
// posebna polja objekta objekta.
SyncDataN: = IterationNo;
SyncDataS: = "Sync" + s;
// a zatim omogućite sinhronizirani poziv metode
Sinhronizuj (SyncMetod1);

//// Slanje sinhronim slanjem poruka (SendMessage)
//// u ovom slučaju podaci se mogu proslijediti i putem parametara poruke (LastRandom),
//// i kroz polja objekta, prosljeđujući adresu instance u parametru poruke
//// objekta toka - Integer (Self).
//// Nedostaci:
//// - nit mora znati rukovanje prozorom obrasca
//// - kao i kod Synchronize, trenutna nit će biti obustavljena sve dok
//// dovršavanje obrade poruke glavnom niti
//// - zahtijeva značajnu količinu CPU vremena za svaki poziv
//// (za prebacivanje niti) stoga je vrlo čest poziv nepoželjan
//// Prednosti:
//// - kao i kod Synchronize, pri obradi poruke možete koristiti
//// sva polja stream objekta (ako je, naravno, proslijeđena njegova adresa)


//// pokretanje teme.
SendMessage (Form1.Handle, WM_USER_SendMessageMetod, Integer (Self), LastRandom);

//// Prijenos putem asinhronog slanja poruka (PostMessage)
//// Budući da u ovom slučaju, do trenutka kada poruku primi glavna nit,
//// tok slanja je možda već dovršen, prosljeđujući adresu instance
//// stream objekt je nevažeći!
//// Nedostaci:
//// - nit mora znati rukovanje prozorom obrasca;
//// - zbog asinhronije prijenos podataka moguć je samo putem parametara
//// poruke, što značajno otežava prijenos podataka velike veličine
//// više od dvije strojne riječi. Pogodno je koristiti za prenošenje cijelog broja itd.
//// Prednosti:
//// - za razliku od prethodnih metoda, trenutna nit NEĆE
//// je pauzirano i odmah će nastaviti s izvršavanjem
//// - za razliku od sinhroniziranog poziva, rukovatelj porukama
//// je metoda obrasca koja mora imati znanje o objektu toka,
//// ili uopće ne znaju ništa o prijenosu ako se podaci samo prenose
//// preko parametara poruke. Odnosno, nit možda ne zna ništa o obrascu.
//// općenito - samo njen Handle, koji se prije može proslijediti kao parametar
//// pokretanje teme.
PostMessage (Form1.Handle, WM_USER_PostMessageMetod, IterationNo, LastRandom);

//// Provjerite mogući završetak

// Provjerite dovršenost prema parametru
ako je zaustavljeno onda prekid;

// Povremeno provjeravajte završetak
ako je IterationNo> = 10 onda Break;

Spavanje (t * 1000); // Zaspajte na t sekundi
end;
end;

procedura tMyThread.SyncMetod1;
početi
// ova metoda se poziva putem metode Synchronize.
// To jest, unatoč činjenici da je to metoda niti tMyThread,
// radi u kontekstu glavne niti aplikacije.
// Stoga, može sve, dobro ili skoro sve :)
// Ali zapamtite, ne vrijedi dugo se "petljati" ovdje

// Proslijeđene parametre možemo izdvojiti iz posebnih polja gdje ih imamo
// spremljeno prije poziva.
Form1.Label1.Caption: = SyncDataS;

// bilo iz drugih polja stream objekta, na primjer, odražavajući njegovo trenutno stanje
Form1.Label2.Caption: = Format ("% d% d",);
end;

Općenito, primjeru je prethodilo moje sljedeće obrazloženje na tu temu ...

Prvo:
NAJVAŽNIJE pravilo višeslojnog programiranja u Delphiju je:
U kontekstu ne-glavne niti, ne možete pristupiti svojstvima i metodama obrazaca, pa čak ni svim komponentama koje "rastu" iz tWinControl-a.

To znači (donekle pojednostavljeno) da niti u metodi Execute naslijeđenoj od TThread -a, niti u drugim metodama / procedurama / funkcijama pozvanim iz Execute, to je zabranjeno izravno pristupite svim svojstvima i metodama vizualnih komponenti.

Kako to učiniti ispravno.
Ne postoje jedinstveni recepti. Točnije, postoji toliko mnogo različitih opcija koje, ovisno o konkretnom slučaju, morate odabrati. Stoga se pozivaju na članak. Pročitavši i razumjevši, programer će moći razumjeti i kako to najbolje učiniti u određenom slučaju.

Ukratko o prstima:

Najčešće višenavojna aplikacija postaje ili kada je potrebno obaviti neku vrstu dugotrajnog posla, ili kada je moguće istovremeno raditi nekoliko stvari koje ne opterećuju procesor.

U prvom slučaju, implementacija rada unutar glavne niti dovodi do "usporavanja" korisničkog sučelja - dok se posao obavlja, petlja poruka se ne izvršava. Kao rezultat toga, program ne reagira na radnje korisnika, a obrazac se ne crta, na primjer, nakon što ga korisnik premjesti.

U drugom slučaju, kada rad uključuje aktivnu razmjenu s vanjskim svijetom, tada tokom prisilnog "zastoja". Dok čekate na primanje / slanje podataka, možete paralelno raditi nešto drugo, na primjer, ponovo slati / primati podatke.

Postoje i drugi slučajevi, ali rjeđe. Međutim, to nije važno. Sada se ne radi o tome.

E sad, kako je sve napisano. Naravno, uzima se u obzir određeni najčešći slučaj, donekle generaliziran. Dakle.

Rad koji se obavlja u zasebnoj niti, u opštem slučaju, ima četiri entiteta (ne znam kako bih to preciznije nazvao):
1. Početni podaci
2. Zapravo sam rad (može zavisiti od početnih podataka)
3. Posredni podaci (na primjer, informacije o trenutnom stanju izvršenja posla)
4. Izlazni podaci (rezultat)

Najčešće se vizualne komponente koriste za čitanje i prikaz većine podataka. Ali, kao što je gore spomenuto, ne možete izravno pristupiti vizualnim komponentama iz toka. Kako biti?
Delphi programeri predlažu upotrebu metode Synchronize klase TThread. Ovdje neću opisivati ​​kako ga koristiti - za to postoji gore spomenuti članak. Dopustite mi samo da kažem da njegova primjena, čak i ispravna, nije uvijek opravdana. Postoje dva problema:

Prvo, tijelo metode koja se zove putem Synchronize uvijek se izvršava u kontekstu glavne niti, pa se, dok se izvršava, petlja prozorske poruke opet ne izvršava. Stoga se mora brzo izvršiti, u protivnom ćemo dobiti sve iste probleme kao i kod jednonavojne implementacije. U idealnom slučaju, metoda koja se naziva putem Synchronize općenito bi se trebala koristiti samo za pristup svojstvima i metodama vizualnih objekata.

Drugo, izvršavanje metode putem Synchronize je "skupo" zadovoljstvo zbog potrebe za dva prebacivanja između niti.

Štoviše, oba su problema međusobno povezana i uzrokuju kontradikciju: s jedne strane, da biste riješili prvi, morate "samljeti" metode pozvane kroz Synchronize, a s druge ih je potrebno češće pozivati, gubeći dragocjeno resursi procesora.

Stoga je, kao i uvijek, potrebno pristupiti razumno, au različitim slučajevima koristiti različite metode interakcije toka s vanjskim svijetom:

Početni podaci
Svi podaci koji se prenose u tok, a ne mijenjaju se tokom rada, moraju se prenijeti čak i prije nego što počne, tj. prilikom stvaranja prijenosa. Da biste ih koristili u tijelu niti, morate napraviti njihovu lokalnu kopiju (obično u poljima potomka TThread).
Ako postoje početni podaci koji se mogu promijeniti dok je nit pokrenuta, tada se takvim podacima mora pristupiti ili putem sinkroniziranih metoda (metode pozvane putem Synchronize), ili kroz polja objekta niti (potomak TThread -a). Ovo posljednje zahtijeva određeni oprez.

Srednji i izlazni podaci
Ovdje, opet, postoji nekoliko načina (prema mojim željama):
- Način asinhronog slanja poruka u glavni prozor aplikacije.
Obično se koristi za slanje poruka o napretku procesa u glavni prozor aplikacije, s prijenosom male količine podataka (na primjer, postotak završetka)
- Način sinhronog slanja poruka u glavni prozor aplikacije.
Obično se koristi u iste svrhe kao i asinhrono slanje, ali vam omogućuje prijenos veće količine podataka, bez stvaranja zasebne kopije.
- Sinhronizovane metode, ako je moguće, kombinujući prenos što je moguće više podataka u jednu metodu.
Može se koristiti i za preuzimanje podataka iz obrasca.
- Kroz polja stream objekta, pružajući međusobno isključiv pristup.
Više detalja možete pronaći u članku.

Eh. Nije uspjelo kratko vrijeme