Kādiem mērķiem tiek izmantotas daudzpavedienu sistēmas. Astoņi vienkārši noteikumi daudzšķiedru lietojumprogrammu izstrādei

Kāda tēma iesācējiem rada visvairāk jautājumu un grūtību? Kad es par to jautāju savam skolotājam un Java programmētājam Aleksandram Prjahinam, viņš uzreiz atbildēja: “Vairāku pavedienu”. Paldies viņam par ideju un palīdzību šī raksta sagatavošanā!

Mēs ieskatīsimies lietojumprogrammas iekšējā pasaulē un tās procesos, noskaidrosim, kāda ir daudzpavedienu būtība, kad tā ir noderīga un kā to ieviest - izmantojot Java kā piemēru. Ja jūs mācāties citu OOP valodu, neuztraucieties: pamatprincipi ir vienādi.

Par straumēm un to izcelsmi

Lai saprastu daudzpavedienu, vispirms sapratīsim, kas ir process. Process ir virtuālās atmiņas un resursu daļa, ko OS piešķir programmas palaišanai. Ja atverat vairākus vienas lietojumprogrammas gadījumus, sistēma katram piešķir procesu. Mūsdienu pārlūkprogrammās par katru cilni var būt atbildīgs atsevišķs process.

Jūs, iespējams, esat saskāries ar Windows "uzdevumu pārvaldnieku" (operētājsistēmā Linux tas ir "sistēmas monitors") un jūs zināt, ka nevajadzīgi darbības procesi ielādē sistēmu, un "smagākie" no tiem bieži iesaldē, tāpēc tie ir jāpārtrauc piespiedu kārtā .

Bet lietotājiem patīk daudzuzdevumu veikšana: nebarojiet viņiem maizi - ļaujiet viņiem atvērt duci logu un lēkt uz priekšu un atpakaļ. Pastāv dilemma: jums ir jānodrošina vienlaicīga lietojumprogrammu darbība un vienlaikus jāsamazina sistēmas slodze, lai tā nepalēninātos. Pieņemsim, ka aparatūra nevar sekot līdzi īpašnieku vajadzībām - problēma jāatrisina programmatūras līmenī.

Mēs vēlamies, lai procesors izpildītu vairāk instrukciju un apstrādātu vairāk datu laika vienībā. Tas ir, mums ir jāiekļauj vairāk izpildītā koda katrā laika šķēlītē. Domājiet par koda izpildes vienību kā objektu - tas ir pavediens.

Sarežģītam gadījumam ir vieglāk piekļūt, ja to sadalāt vairākās vienkāršās lietās. Tātad, strādājot ar atmiņu: "smags" process ir sadalīts pavedienos, kas aizņem mazāk resursu un, visticamāk, piegādās kodu kalkulatoram (kā tieši - skatīt zemāk).

Katrā lietojumprogrammā ir vismaz viens process, un katram procesam ir vismaz viens pavediens, ko sauc par galveno pavedienu un no kura vajadzības gadījumā tiek palaisti jauni pavedieni.

Atšķirība starp pavedieniem un procesiem

    Pavedieni izmanto procesam piešķirto atmiņu, un procesiem ir nepieciešama sava atmiņas vieta. Tāpēc pavedieni tiek izveidoti un pabeigti ātrāk: sistēmai nav katru reizi jāpiešķir tiem jauna adreses telpa un pēc tam jāatbrīvo.

    Procesi katrs strādā ar saviem datiem - viņi var kaut ko apmainīties, tikai izmantojot starpprocesu saziņas mehānismu. Pavedieni tieši piekļūst viens otra datiem un resursiem: tas, kas ir mainīts, ir uzreiz pieejams ikvienam. Vītne var kontrolēt "kolēģi" procesā, bet process kontrolē tikai savas "meitas". Tāpēc pārslēgšanās starp straumēm notiek ātrāk un saziņa starp tām ir vieglāka.

Kāds ir secinājums no tā? Ja jums pēc iespējas ātrāk jāapstrādā liels datu apjoms, sadaliet to gabalos, kurus var apstrādāt ar atsevišķiem pavedieniem, un pēc tam salieciet rezultātu. Tas ir labāk nekā resursu izsalkuši procesi.

Bet kāpēc tāda populāra lietojumprogramma kā Firefox izvēlas vairāku procesu izveidi? Tā kā pārlūkprogrammai izolēto cilņu darbība ir uzticama un elastīga. Ja kaut kas nav kārtībā ar vienu procesu, nav nepieciešams pārtraukt visu programmu - ir iespējams saglabāt vismaz daļu datu.

Kas ir daudzpavedieni

Tātad mēs nonākam pie galvenā punkta. Daudzpavedieni ir tad, kad pieteikšanās process tiek sadalīts pavedienos, kurus procesors paralēli apstrādā vienā laika vienībā.

Skaitļošanas slodze tiek sadalīta starp diviem vai vairākiem kodoliem, lai saskarne un citi programmas komponenti nepalēninātu viens otra darbu.

Vairāku pavedienu lietojumprogrammas var palaist ar viena kodola procesoriem, bet pēc tam pavedieni tiek izpildīti pēc kārtas: pirmais strādāja, tā stāvoklis tika saglabāts - otrais tika atļauts darboties, saglabāts - atgriezts pirmajā vai palaists trešais, utt.

Aizņemti cilvēki sūdzas, ka viņiem ir tikai divas rokas. Procesiem un programmām var būt tik daudz roku, cik nepieciešams, lai pēc iespējas ātrāk pabeigtu uzdevumu.

Gaidiet signālu: sinhronizācija lietojumprogrammās ar vairākiem pavedieniem

Iedomājieties, ka vairāki pavedieni vienlaikus mēģina mainīt vienu un to pašu datu apgabalu. Kuras izmaiņas galu galā tiks pieņemtas un kuras izmaiņas tiks atceltas? Lai izvairītos no neskaidrībām, strādājot ar kopīgiem resursiem, pavedieniem ir jāsaskaņo savas darbības. Lai to izdarītu, viņi apmainās ar informāciju, izmantojot signālus. Katrs pavediens stāsta citiem, ko tā dara un kādas izmaiņas gaidāmas. Tātad visu pavedienu dati par pašreizējo resursu stāvokli tiek sinhronizēti.

Pamata sinhronizācijas rīki

Savstarpēja izslēgšana (savstarpēja izslēgšana, saīsināti - mutex) - "karogs", kas iet uz pavedienu, kuram pašlaik ir atļauts strādāt ar kopīgiem resursiem. Novērš citu pavedienu piekļuvi aizņemtajai atmiņas zonai. Lietojumprogrammā var būt vairāki muteksi, un tos var koplietot starp procesiem. Ir kāda nianse: mutex piespiež lietojumprogrammu katru reizi piekļūt operētājsistēmas kodolam, kas ir dārgi.

Semafors - ļauj ierobežot pavedienu skaitu, kas noteiktā brīdī var piekļūt resursam. Tas samazinās procesora slodzi, izpildot kodu vietās, kur ir vājās vietas. Problēma ir tā, ka optimālais pavedienu skaits ir atkarīgs no lietotāja mašīnas.

Pasākums - jūs definējat nosacījumu, kura gadījumā kontrole tiek pārnesta uz vēlamo pavedienu. Straumēs tiek apmainīti notikumu dati, lai attīstītu un loģiski turpinātu viens otra darbības. Viens saņēma datus, otrs pārbaudīja to pareizību, trešais tos saglabāja cietajā diskā. Pasākumi atšķiras ar to, kā tie tiek atcelti. Ja jums ir jāpaziņo vairāki pavedieni par notikumu, jums būs manuāli jāiestata atcelšanas funkcija, lai apturētu signālu. Ja ir tikai viens mērķa pavediens, varat izveidot automātiskās atiestatīšanas notikumu. Pēc plūsmas sasniegšanas tas pārtrauks pašu signālu. Notikumus var ievietot rindā, lai nodrošinātu elastīgu plūsmas kontroli.

Kritiskā sadaļa - sarežģītāks mehānisms, kas apvieno cilpas skaitītāju un semaforu. Skaitītājs ļauj atlikt semafora sākšanu uz vēlamo laiku. Priekšrocība ir tāda, ka kodols tiek aktivizēts tikai tad, ja sadaļa ir aizņemta un ir jāieslēdz semafors. Pārējā laikā pavediens darbojas lietotāja režīmā. Diemžēl sadaļu var izmantot tikai viena procesa ietvaros.

Kā ieviest daudzpavedienu Java

Vītņu klase ir atbildīga par darbu ar pavedieniem Java. Jaunas pavediena izveide uzdevuma izpildei nozīmē izveidot pavedienu klases instanci un saistīt to ar vēlamo kodu. To var izdarīt divos veidos:

    apakšklase Vītne;

    ieviest Runnable saskarni savā klasē un pēc tam nodot klases gadījumus pavedienu konstruktoram.

Lai gan mēs nepieskarsimies strupceļu (strupceļu) tēmai, kad pavedieni bloķē viens otra darbu un karājas, mēs to atstāsim nākamajam rakstam.

Java daudzpavedienu piemērs: galda teniss ar muteksiem

Ja domājat, ka notiks kaut kas briesmīgs, izelpojiet. Mēs apsvērsim iespēju strādāt ar sinhronizācijas objektiem gandrīz rotaļīgā veidā: divus pavedienus iemetīs mutex.Bet patiesībā jūs redzēsit reālu lietojumprogrammu, kurā tikai viens pavediens var apstrādāt publiski pieejamus datus vienlaikus.

Vispirms izveidosim klasi, kas manto jau zināmās pavediena īpašības, un uzrakstīsim kickBall metodi:

Publiskā klase PingPongThread paplašina pavedienu (PingPongThread (String name) (this.setName (name); // ignorēt pavediena nosaukumu) @Override public void run () (Ball ball = Ball.getBall (); while (ball.isInGame () ) (kickBall (bumba);)) private void kickBall (Ball ball) (if (! ball.getSide (). equals (getName ())) (ball.kick (getName ());)))

Tagad parūpēsimies par bumbu. Ar mums viņš nebūs vienkāršs, bet neaizmirstams: lai viņš varētu pateikt, kurš viņu sita, no kuras puses un cik reizes. Lai to izdarītu, mēs izmantojam mutex: tas apkopos informāciju par katra pavediena darbu - tas ļaus izolētiem pavedieniem sazināties savā starpā. Pēc 15. sitiena mēs izņemsim bumbu no spēles, lai to nopietni nesavainotu.

Publiskas klases bumba (privāti int sitieni = 0; privāta statiska bumbas instance = jauna bumba (); privāta virknes puse = ""; privāta bumba () () statiska bumba getBall () (atgriešanās instance;) sinhronizēts tukšs sitiens (virknes atskaņošanas vārds) (kicks ++; side = playername; System.out.println (kicks + "" + side);) String getSide () (return side;) boolean isInGame () (return (kicks< 15); } }

Un tagad uz skatuves ienāk divi spēlētāju pavedieni. Sauksim viņus bez liekas piepūles par Ping un Pong:

Publiskās klases PingPongGame (PingPongThread player1 = jauns PingPongThread ("Ping"); PingPongThread player2 = jauns PingPongThread ("Pong"); Ball ball; PingPongGame () (ball = Ball.getBall ();) void startGame () throws InterruptException .start (); player2.start ();))

"Pilns stadions cilvēku - laiks sākt maču." Mēs oficiāli paziņosim par sanāksmes atklāšanu - pieteikuma galvenajā klasē:

Publiskās klases PingPong (publisks statisks void main (String args) metieni InterruptException (PingPongGame spēle = new PingPongGame (); game.startGame ();))

Kā redzat, šeit nav nekā nikna. Šis pagaidām ir tikai ievads daudzpavedieniem, taču jūs jau zināt, kā tas darbojas, un varat eksperimentēt - ierobežojiet spēles ilgumu nevis ar sitienu skaitu, bet, piemēram, pēc laika. Vēlāk atgriezīsimies pie vairāku pavedienu tēmas - aplūkosim java.util.concrent paketi, Akka bibliotēku un nepastāvīgo mehānismu. Parunāsim arī par vairāku pavedienu ieviešanu Python.

Daudzpavedienu programmēšana būtiski neatšķiras no notikumu vadītu grafisku lietotāja saskarņu rakstīšanas vai pat vienkāršu secīgu lietojumprogrammu rakstīšanas. Šeit tiek piemēroti visi svarīgi noteikumi, kas reglamentē iekapsulēšanu, bažu nošķiršanu, vaļīgu savienošanu utt. Bet daudziem izstrādātājiem ir grūti rakstīt daudzpavedienu programmas tieši tāpēc, ka viņi ignorē šos noteikumus. Tā vietā viņi cenšas īstenot praksē daudz mazāk svarīgās zināšanas par pavedieniem un sinhronizācijas primitīviem, kas iegūti no tekstiem par vairāku pavedienu programmēšanu iesācējiem.

Tātad, kādi ir šie noteikumi

Cits programmētājs, saskaroties ar problēmu, domā: "Ak, tieši tā, mums ir jāpiemēro regulārās izteiksmes." Un tagad viņam jau ir divas problēmas - Džeimijs Zavinskis.

Cits programmētājs, saskaroties ar problēmu, domā: "Ak, labi, es šeit izmantošu straumes." Un tagad viņam ir desmit problēmas - Bils Šindlers.

Pārāk daudz programmētāju, kuri apņemas rakstīt vairāku pavedienu kodu, iekrīt slazdā, piemēram, Gētes balādes varonis " Burvja māceklis". Programmētājs iemācīsies izveidot virkni pavedienu, kas principā darbojas, bet agrāk vai vēlāk tie kļūst nekontrolējami, un programmētājs nezina, ko darīt.

Bet atšķirībā no pamestā vedņa, nelaimīgais programmētājs nevar cerēt uz spēcīga burvja ierašanos, kurš vicinās ar zizli un atjaunos kārtību. Tā vietā programmētājs dodas uz visneizskatīgākajiem trikiem, cenšoties tikt galā ar pastāvīgi aktuālām problēmām. Rezultāts vienmēr ir tāds pats: tiek iegūts pārāk sarežģīts, ierobežots, trausls un neuzticams lietojums. Tam ir mūžīgi strupceļa draudi un citas briesmas, kas raksturīgas sliktam vairāku pavedienu kodam. Es pat nerunāju par neizskaidrojamām avārijām, sliktu sniegumu, nepilnīgiem vai nepareiziem darba rezultātiem.

Jūs, iespējams, domājāt: kāpēc tas notiek? Izplatīts nepareizs priekšstats ir šāds: "Vairāku pavedienu programmēšana ir ļoti grūta." Bet tas tā nav. Ja vairāku pavedienu programma nav uzticama, tad tā parasti neizdodas tādu pašu iemeslu dēļ kā zemas kvalitātes viena pavediena programma. Vienkārši programmētājs neievēro pamata, labi zināmās un pārbaudītās attīstības metodes. Šķiet, ka daudzpavedienu programmas ir tikai sarežģītākas, jo, jo vairāk paralēlu pavedienu noiet greizi, jo vairāk putru tie rada - un daudz ātrāk nekā viens pavediens.

Nepareizs priekšstats par "daudzpavedienu programmēšanas sarežģītību" ir kļuvis plaši izplatīts, jo tie izstrādātāji, kuri ir profesionāli attīstījušies, rakstot viena pavediena kodu, pirmo reizi saskārās ar daudzšķiedru pavedienu un netika ar to galā. Bet tā vietā, lai pārdomātu savus aizspriedumus un darba ieradumus, viņi spītīgi nosaka to, ka nevēlas nekādā veidā strādāt. Attaisnojoties par neuzticamu programmatūru un nokavētiem termiņiem, šie cilvēki atkārto vienu un to pašu: "daudzpavedienu programmēšana ir ļoti grūta."

Lūdzu, ņemiet vērā, ka iepriekš es runāju par tipiskām programmām, kurās tiek izmantota vairāku pavedienu izmantošana. Patiešām, ir sarežģīti vairāku pavedienu scenāriji, kā arī sarežģīti vienas vītnes scenāriji. Bet tie nav izplatīti. Kā likums, praksē no programmētāja netiek prasīts nekas pārdabisks. Mēs pārvietojam datus, pārveidojam tos, laiku pa laikam veicam dažus aprēķinus un, visbeidzot, saglabājam informāciju datu bāzē vai parādām to ekrānā.

Nav nekas grūts, uzlabojot vidējo vienas vītnes programmu un pārvēršot to par vairākiem pavedieniem. Vismaz tā nevajadzētu būt. Grūtības rodas divu iemeslu dēļ:

  • programmētāji neprot pielietot vienkāršas, labi zināmas pārbaudītas izstrādes metodes;
  • lielākā daļa informācijas, kas sniegta grāmatās par vairāku pavedienu programmēšanu, ir tehniski pareiza, bet pilnīgi nepiemērojama lietišķo problēmu risināšanai.

Vissvarīgākās programmēšanas koncepcijas ir universālas. Tie ir vienlīdz attiecināmi uz vienas un vairāku pavedienu programmām. Programmētāji, kas slīkst straumju virpulī, vienkārši neapgūst svarīgas mācības, apgūstot viena pavediena kodu. Es varu to teikt, jo šādi izstrādātāji pieļauj tās pašas fundamentālās kļūdas daudzpavedienu un vienas pavedienu programmās.

Varbūt vissvarīgākā mācība, kas jāapgūst sešdesmit gadu programmēšanas vēsturē, ir šāda: globāls mainīgs stāvoklis- ļauns... Patiess ļaunums. Programmas, kuru pamatā ir globāli mainīgs stāvoklis, ir samērā grūti pamatotas, un tās parasti nav uzticamas, jo ir pārāk daudz veidu, kā mainīt stāvokli. Ir bijis daudz pētījumu, kas apstiprina šo vispārējo principu, ir neskaitāmi dizaina modeļi, kuru galvenais mērķis ir īstenot vienu vai otru datu slēpšanas metodi. Lai padarītu savas programmas paredzamākas, mēģiniet pēc iespējas novērst mainīgo stāvokli.

Vienā vītņotā secīgā programmā datu sabojāšanas iespējamība ir tieši proporcionāla komponentu skaitam, kas var modificēt datus.

Kā likums, nav iespējams pilnībā atbrīvoties no globālā stāvokļa, taču izstrādātāja arsenālā ir ļoti efektīvi rīki, kas ļauj stingri kontrolēt, kuras programmas sastāvdaļas var mainīt stāvokli. Turklāt mēs uzzinājām, kā izveidot ierobežojošus API slāņus ap primitīvām datu struktūrām. Tāpēc mums ir laba kontrole pār to, kā mainās šīs datu struktūras.

Globāli mainīga stāvokļa problēmas pakāpeniski kļuva redzamas 80. gadu beigās un 90. gadu sākumā, izplatoties notikumu vadītai programmēšanai. Programmas vairs nesākās "no sākuma" vai sekoja vienam, paredzamam izpildes ceļam "līdz beigām". Mūsdienu programmām ir sākotnējais stāvoklis, pēc iziešanas tajās notiek notikumi - neparedzamā secībā, ar mainīgiem laika intervāliem. Kods paliek vienā pavedienā, bet jau kļūst asinhrons. Datu sabojāšanas varbūtība palielinās tieši tāpēc, ka notikumu parādīšanās secība ir ļoti svarīga. Šāda veida situācijas ir diezgan izplatītas: ja notikums B notiek pēc notikuma A, tad viss darbojas labi. Bet, ja notikums A notiek pēc notikuma B un notikumam C ir laiks iejaukties starp tiem, tad dati var tikt izkropļoti līdz nepazīšanai.

Ja tiek iesaistītas paralēlas plūsmas, problēma vēl vairāk saasinās, jo vairākas metodes vienlaikus var darboties globālā stāvoklī. Kļūst neiespējami precīzi spriest, kā mainās globālais stāvoklis. Mēs jau runājam ne tikai par to, ka notikumi var notikt neparedzamā secībā, bet arī par to, ka var tikt atjaunināts vairāku izpildes pavedienu stāvoklis. vienlaicīgi... Izmantojot asinhrono programmēšanu, jūs varat vismaz nodrošināt, lai kāds notikums nevarētu notikt pirms cita notikuma apstrādes pabeigšanas. Tas ir, ir iespējams droši pateikt, kāda būs globālā valsts konkrēta notikuma apstrādes beigās. Vairāku pavedienu kodā parasti nav iespējams pateikt, kuri notikumi notiks paralēli, tāpēc nav iespējams droši aprakstīt globālo stāvokli jebkurā brīdī.

Daudzšķiedru programma ar plašu globāli mainīgu stāvokli ir viens no daiļrunīgākajiem man zināmā Heisenbergas nenoteiktības principa piemēriem. Nav iespējams pārbaudīt programmas stāvokli, nemainot tās uzvedību.

Kad es sāku kārtējo filozofiju par globālo mainīgo stāvokli (būtība ir izklāstīta dažās iepriekšējās rindkopās), programmētāji izpleš acis un apliecina, ka viņi to visu zina jau sen. Bet, ja jūs to zināt, kāpēc jūs nevarat pateikt no sava koda? Programmas ir pārpildītas ar globālu mainīgu stāvokli, un programmētāji brīnās, kāpēc kods nedarbojas.

Nav pārsteidzoši, ka vissvarīgākais darbs ar daudzpavedienu programmēšanu notiek projektēšanas posmā. Ir skaidri jānosaka, kas programmai jādara, jāizstrādā neatkarīgi moduļi, lai veiktu visas funkcijas, sīki jāapraksta, kādi dati ir nepieciešami šim modulim, un jānosaka veidi, kā apmainīties ar informāciju starp moduļiem ( Jā, neaizmirstiet sagatavot jaukus T-kreklus visiem projektā iesaistītajiem. Pirmā lieta.- apm. red. oriģinālā). Šis process būtiski neatšķiras no vienas pavediena programmas izstrādes. Panākumu atslēga, tāpat kā viena pavediena kods, ir ierobežot moduļu mijiedarbību. Ja jūs varat atbrīvoties no kopīga mainīga stāvokļa, datu koplietošanas problēmas vienkārši neradīsies.

Kāds varētu iebilst, ka dažreiz nav laika tik delikātam programmas noformējumam, kas ļaus iztikt bez globālās valsts. Es uzskatu, ka tam ir iespējams un nepieciešams veltīt laiku. Nekas neietekmē daudzpavedienu programmas tik destruktīvi kā mēģinājumi tikt galā ar globālo mainīgo stāvokli. Jo detalizētāk jāpārvalda, jo lielāka iespēja, ka jūsu programma sasniegs maksimumu un avarēs.

Reālistiskos lietojumos jābūt noteiktam kopīgam stāvoklim, kas var mainīties. Un tieši šeit lielākajai daļai programmētāju rodas problēmas. Programmētājs redz, ka šeit ir nepieciešams kopīgs stāvoklis, pievēršas daudzšķiedru arsenālam un paņem no turienes vienkāršāko rīku: universālu slēdzeni (kritiskā sadaļa, mutex vai kā viņi to sauc). Šķiet, ka viņi uzskata, ka savstarpēja izslēgšana atrisinās visas datu koplietošanas problēmas.

Problēmu skaits, kas var rasties ar šādu vienu slēdzeni, ir satriecošs. Jāņem vērā sacensību apstākļi, problēmas ar pārāk plašu bloķēšanu un sadales taisnīguma jautājumi ir tikai daži piemēri. Ja jums ir vairākas slēdzenes, īpaši, ja tās ir ligzdotas, jums būs jāveic arī pasākumi, lai novērstu strupceļu, dinamisku bloķēšanu, rindu bloķēšanu un citus draudus, kas saistīti ar vienlaicīgumu. Turklāt pastāv arī vienas bloķēšanas raksturīgās problēmas.
Rakstot vai pārskatot kodu, man ir gandrīz nemaldīgs dzelzs noteikums: ja esi uztaisījis slēdzeni, tad šķiet, ka esi kaut kur kļūdījies.

Šo apgalvojumu var komentēt divos veidos:

  1. Ja jums ir nepieciešama bloķēšana, iespējams, jums ir globāls mainīgs stāvoklis, kuru vēlaties aizsargāt pret vienlaicīgiem atjauninājumiem. Globāla mainīga stāvokļa klātbūtne ir kļūda lietojumprogrammu izstrādes posmā. Pārskatīšana un pārveidošana.
  2. Pareizi izmantot slēdzenes nav viegli, un var būt neticami grūti lokalizēt ar bloķēšanu saistītās kļūdas. Ļoti iespējams, ka slēdzeni izmantosit nepareizi. Ja es redzu slēdzeni un programma uzvedas neparasti, tad pirmā lieta, ko es daru, ir pārbaudīt kodu, kas ir atkarīgs no slēdzenes. Un es parasti tajā atrodu problēmas.

Abas šīs interpretācijas ir pareizas.

Daudzpavedienu koda rakstīšana ir vienkārša. Bet ir ļoti, ļoti grūti pareizi lietot sinhronizācijas primitīvus. Varbūt jūs neesat kvalificēts pareizi izmantot pat vienu slēdzeni. Galu galā slēdzenes un citi sinhronizācijas primitīvi ir konstrukcijas, kas uzceltas visas sistēmas līmenī. Cilvēki, kuri daudz labāk nekā jūs saprot paralēlo programmēšanu, izmanto šos primitīvus, lai izveidotu vienlaicīgas datu struktūras un augsta līmeņa sinhronizācijas konstrukcijas. Un jūs un es, parastie programmētāji, vienkārši ņemam šādas konstrukcijas un izmantojam tās savā kodā. Lietojumprogrammu programmētājam nevajadzētu izmantot zema līmeņa sinhronizācijas primitīvus biežāk, nekā viņš veic tiešus zvanus uz ierīču draiveriem. Tas ir, gandrīz nekad.

Mēģinājums izmantot slēdzenes datu koplietošanas problēmu risināšanai ir kā ugunsgrēka dzēšana ar šķidru skābekli. Tāpat kā ugunsgrēku, šādas problēmas ir vieglāk novērst nekā novērst. Ja atbrīvosities no koplietojamā stāvokļa, jums arī nav ļaunprātīgi jāizmanto sinhronizācijas primitīvi.

Lielākajai daļai to, ko jūs zināt par daudzpavedienu, nav nozīmes

Vairāku pavedienu apmācībās iesācējiem jūs uzzināsit, kas ir pavedieni. Tad autors sāks apsvērt dažādus veidus, kā šie pavedieni var darboties paralēli - piemēram, runāt par piekļuves kontrolēšanu koplietotiem datiem, izmantojot slēdzenes un semaforus, un pakavēties pie tā, kas var notikt, strādājot ar notikumiem. Rūpīgi apskatīs stāvokļa mainīgos, atmiņas barjeras, kritiskās sadaļas, mutexes, gaistošos laukus un atomu operācijas. Tiks apskatīti piemēri, kā izmantot šīs zema līmeņa konstrukcijas, lai veiktu visu veidu sistēmas darbības. Izlasījis šo materiālu līdz pusei, programmētājs nolemj, ka viņš jau pietiekami zina par visiem šiem primitīviem un to izmantošanu. Galu galā, ja es zinu, kā šī lieta darbojas sistēmas līmenī, es to varu pielietot tāpat kā lietojumprogrammu līmenī. Jā?

Iedomājieties, ka pusaudzim pastāstāt, kā pašam salikt iekšdedzes dzinēju. Tad bez jebkādas braukšanas apmācības tu viņu iesēdini pie automašīnas stūres un saki: "Brauc!" Pusaudzis saprot, kā darbojas automašīna, bet viņam nav ne jausmas, kā tajā nokļūt no punkta A līdz punktam B.

Izpratne par to, kā pavedieni darbojas sistēmas līmenī, parasti nekādā veidā nepalīdz lietojumprogrammu līmenī. Es nesaku, ka programmētājiem nav jāapgūst visas šīs zema līmeņa detaļas. Vienkārši negaidiet, ka varēsit šīs zināšanas pielietot uzreiz, izstrādājot vai izstrādājot biznesa lietojumprogrammu.

Ievadvītņu literatūrā (un ar to saistītajos akadēmiskajos kursos) nevajadzētu pētīt šādas zema līmeņa konstrukcijas. Jums jākoncentrējas uz visbiežāk sastopamo problēmu risināšanu un jāparāda izstrādātājiem, kā šīs problēmas tiek atrisinātas, izmantojot augsta līmeņa iespējas. Principā lielākā daļa biznesa lietojumprogrammu ir ārkārtīgi vienkāršas programmas. Viņi nolasa datus no vienas vai vairākām ievades ierīcēm, veic šo datu sarežģītu apstrādi (piemēram, šajā procesā viņi pieprasa vēl dažus datus) un pēc tam izvada rezultātus.

Šīs programmas bieži lieliski iekļaujas pakalpojumu sniedzēja un patērētāja modelī, kuram nepieciešami tikai trīs pavedieni:

  • ievades plūsma nolasa datus un ievieto tos ievades rindā;
  • darbinieka pavediens nolasa ierakstus no ievades rindas, apstrādā tos un ievieto rezultātus izvades rindā;
  • izvades plūsma nolasa ierakstus no izvades rindas un saglabā tos.

Šie trīs pavedieni darbojas neatkarīgi, saziņa starp tiem notiek rindas līmenī.

Lai gan tehniski šīs rindas var uzskatīt par kopīga stāvokļa zonām, praksē tās ir tikai saziņas kanāli, kuros darbojas viņu iekšējā sinhronizācija. Rindas atbalsta darbu ar daudziem ražotājiem un patērētājiem vienlaikus, jūs varat vienlaikus pievienot un noņemt vienumus.

Tā kā ievades, apstrādes un izvades posmi ir izolēti viens no otra, to ieviešanu var viegli mainīt, neietekmējot pārējo programmu. Kamēr rindā esošo datu veids nemainās, varat pārveidot atsevišķas programmas sastāvdaļas pēc saviem ieskatiem. Turklāt, tā kā rindā piedalās patvaļīgs piegādātāju un patērētāju skaits, nav grūti pievienot citus ražotājus / patērētājus. Mums var būt desmitiem ievades plūsmu, kas raksta informāciju vienā rindā, vai desmitiem darbinieku pavedienu, kas ņem informāciju no ievades rindas un apkopo datus. Viena datora ietvaros šāds modelis labi mērogojas.

Vissvarīgākais ir tas, ka mūsdienu programmēšanas valodas un bibliotēkas ļauj ļoti viegli izveidot ražotāja un patērētāja lietojumprogrammas. .NET atradīsit Paralēlās kolekcijas un TPL datu plūsmas bibliotēku. Java piedāvā pakalpojumu Executor, kā arī BlockingQueue un citas klases no java.util.concurrent nosaukumvietas. C ++ ir Boost pavedienu bibliotēka un Intel Thread Building Blocks bibliotēka. Microsoft Visual Studio 2013 ievieš asinhronos aģentus. Līdzīgas bibliotēkas ir pieejamas arī Python, JavaScript, Ruby, PHP un, cik man zināms, daudzās citās valodās. Jūs varat izveidot ražotāja-patērētāja lietojumprogrammu, izmantojot jebkuru no šīm pakotnēm, nekad neizmantojot slēdzenes, semaforus, nosacījumu mainīgos vai citus sinhronizācijas primitīvus.

Šajās bibliotēkās tiek brīvi izmantoti dažādi sinhronizācijas primitīvi. Tas ir labi. Visas šīs bibliotēkas raksta cilvēki, kuri neskaitāmi labāk saprot daudzpavedienu nekā vidējais programmētājs. Darbs ar šādu bibliotēku ir praktiski tas pats, kas izmantot izpildlaika valodu bibliotēku. To var salīdzināt ar programmēšanu augsta līmeņa valodā, nevis montāžas valodā.

Piegādātāja un patērētāja modelis ir tikai viens no daudziem piemēriem. Iepriekš minētajās bibliotēkās ir klases, kuras var izmantot, lai ieviestu daudzus izplatītākos pavedienu dizaina modeļus, neiedziļinoties zema līmeņa detaļās. Ir iespējams izveidot liela mēroga daudzšķiedru lietojumprogrammas, neuztraucoties par to, kā pavedieni tiek koordinēti un sinhronizēti.

Darbs ar bibliotēkām

Tātad vairāku pavedienu programmu izveide būtiski neatšķiras no sinhrono programmu ar vienu pavedienu rakstīšanas. Svarīgi iekapsulēšanas un datu slēpšanas principi ir universāli, un to nozīme pieaug tikai tad, ja ir iesaistīti vairāki vienlaicīgi pavedieni. Ja jūs ignorējat šos svarīgos aspektus, tad pat visplašākās zināšanas par zema līmeņa vītņošanu jūs neglābs.

Mūsdienu izstrādātājiem ir jāatrisina daudzas problēmas lietojumprogrammu programmēšanas līmenī, gadās, ka vienkārši nav laika domāt par to, kas notiek sistēmas līmenī. Jo sarežģītākas lietojumprogrammas kļūst, jo sarežģītāka informācija ir jāslēpj starp API līmeņiem. Mēs to darām vairāk nekā duci gadu. Var apgalvot, ka sistēmas sarežģītības kvalitatīva slēpšana no programmētāja ir galvenais iemesls, kāpēc programmētājs spēj rakstīt mūsdienīgas lietojumprogrammas. Vai mēs neslēpjam sistēmas sarežģītību, ieviešot lietotāja saskarnes ziņojumu cilpu, veidojot zema līmeņa sakaru protokolus utt.?

Līdzīga situācija ir arī ar daudzpavedienu. Lielākā daļa vairāku pavedienu scenāriju, ar kuriem var saskarties vidusmēra biznesa lietojumprogrammu izstrādātājs, jau ir labi zināmi un labi ieviesti bibliotēkās. Bibliotēkas funkcijas lieliski palīdz slēpt paralēlisma milzīgo sarežģītību. Jums jāiemācās izmantot šīs bibliotēkas tāpat kā lietotāja saskarnes elementu bibliotēkas, sakaru protokolus un daudzus citus rīkus, kas vienkārši darbojas. Zema līmeņa daudzpavedienu atstājiet speciālistu - lietojumprogrammu veidošanā izmantoto bibliotēku autoru - ziņā.

NS Šis raksts nav paredzēts rūdītiem Python pieradinātājiem, kuriem šīs čūsku bumbiņas izjaukšana ir bērnu spēle, bet drīzāk virspusējs pārskats par daudzšķiedru iespējām tikko atkarīgam pitonam.

Diemžēl krievu valodā nav tik daudz materiālu par tēmu par daudzpavedienu Python, un pythoners, kuri neko nebija dzirdējuši, piemēram, par GIL, sāka ar mani apskaužami regulāri. Šajā rakstā es mēģināšu aprakstīt daudzpavedienu pitona pamatfunkcijas, pastāstīt, kas ir GIL un kā ar to sadzīvot (vai bez tā), un vēl daudz ko citu.


Python ir burvīga programmēšanas valoda. Tas lieliski apvieno daudzas programmēšanas paradigmas. Lielākā daļa uzdevumu, ar kuriem var tikt galā programmētājs, šeit tiek atrisināti viegli, eleganti un kodolīgi. Bet visām šīm problēmām bieži vien pietiek ar vienpavedienu risinājumu, un vienas vītnes programmas parasti ir paredzamas un viegli atkļūdojamas. To pašu nevar teikt par daudzpavedienu un daudzprocesu programmām.

Daudzpavedienu lietojumprogrammas


Python ir modulis vītņošana , un tajā ir viss nepieciešamais vairāku pavedienu programmēšanai: ir dažāda veida slēdzenes, kā arī semafors un notikumu mehānisms. Vienā vārdā sakot - viss, kas nepieciešams lielākajai daļai daudzpavedienu programmu. Turklāt visu šo rīku izmantošana ir pavisam vienkārša. Apskatīsim piemēru programmai, kas sāk 2 pavedienus. Viens pavediens raksta desmit "0", otrs - desmit "1" un stingri pēc kārtas.

importēt pavedienus

def rakstnieks

par i xrange (10):

drukāt x

Event_for_set.set ()

# iniciatīvas notikumi

e1 = vītņošana. Notikums ()

e2 = vītņošana. Notikums ()

# init pavedieni

0, e1, e2))

1, e2, e1))

# sākt pavedienus

t1.sākt ()

t2.start ()

t1.pievienoties ()

t2.pievienoties ()


Nav burvju vai voodoo koda. Kods ir skaidrs un konsekvents. Turklāt, kā redzat, mēs esam izveidojuši straumi no funkcijas. Tas ir ļoti ērti maziem darbiem. Šis kods ir arī diezgan elastīgs. Pieņemsim, ka mums ir trešais process, kas raksta “2”, tad kods izskatīsies šādi:

importēt pavedienus

def rakstnieks (x, event_for_wait, event_for_set):

par i xrange (10):

Event_for_wait.wait () # gaidiet notikumu

Event_for_wait.clear () # tīrs notikums nākotnei

drukāt x

Event_for_set.set () # iestatiet notikumu kaimiņu pavedienam

# iniciatīvas notikumi

e1 = vītņošana. Notikums ()

e2 = vītņošana. Notikums ()

e3 = vītņošana. Notikums ()

# init pavedieni

t1 = vītņošana. Vītne (mērķis = rakstnieks, args = ( 0, e1, e2))

t2 = vītņošana. Vītne (mērķis = rakstnieks, args = ( 1, e2, e3))

t3 = vītņošana. Vītne (mērķis = rakstnieks, args = ( 2, e3, e1))

# sākt pavedienus

t1.sākt ()

t2.start ()

t3.sākt ()

e1.set () # uzsākt pirmo notikumu

# pievienojiet pavedienus galvenajam pavedienam

t1.pievienoties ()

t2.pievienoties ()

t3.pievienoties ()


Mēs pievienojām jaunu notikumu, jaunu pavedienu un nedaudz mainījām parametrus, ar kuriem
sākas straumes (protams, jūs varat uzrakstīt vispārīgāku risinājumu, izmantojot, piemēram, MapReduce, bet tas ir ārpus šī raksta darbības jomas).
Kā redzat, burvju joprojām nav. Viss ir vienkāršs un saprotams. Ejam tālāk.

Globālā tulka bloķēšana


Vītņu izmantošanai ir divi visizplatītākie iemesli: pirmkārt, lai palielinātu mūsdienu procesoru daudzkodolu arhitektūras izmantošanas efektivitāti un līdz ar to arī programmas veiktspēju;
otrkārt, ja mums programmas loģika jāsadala paralēlās, pilnīgi vai daļēji asinhronās sadaļās (piemēram, lai varētu pingēt vairākus serverus vienlaicīgi).

Pirmajā gadījumā mēs saskaramies ar šādu Python (vai drīzāk tā galvenās CPython ieviešanas) ierobežojumu, piemēram, Global Interpreter Lock (vai īsumā GIL). GIL koncepcija ir tāda, ka procesors vienlaikus var izpildīt tikai vienu pavedienu. Tas tiek darīts tā, lai starp pavedieniem nebūtu cīņas par atsevišķiem mainīgajiem. Izpildāmā pavediens iegūst piekļuvi visai videi. Šī pavedienu ieviešanas iezīme Python ievērojami vienkāršo darbu ar pavedieniem un nodrošina noteiktu pavedienu drošību.

Bet ir smalks punkts: varētu šķist, ka lietojumprogramma ar vairākiem pavedieniem darbosies tieši tikpat daudz laika kā viena pavediena lietojumprogramma, kas veic to pašu, vai katra CPU pavediena izpildes laika summa. Bet šeit mūs gaida viens nepatīkams efekts. Apsveriet programmu:

ar atvērtu ("test1.txt", "w") kā fout:

par i xrange (1000000):

drukāt >> fout, 1


Šī programma failā vienkārši uzraksta miljonu rindiņu “1” un manā datorā to dara ~ 0,35 sekundēs.

Apsveriet citu programmu:

no pavedienu importēšanas Pavediens

def rakstnieks (faila nosaukums, n):

ar atvērtu (faila nosaukums, "w") kā fout:

par i xrange (n):

drukāt >> fout, 1

t1 = pavediens (mērķis = rakstnieks, args = ("test2.txt", 500000,))

t2 = pavediens (mērķis = rakstnieks, args = ("test3.txt", 500000,))

t1.sākt ()

t2.start ()

t1.pievienoties ()

t2.pievienoties ()


Šī programma izveido 2 pavedienus. Katrā pavedienā tas atsevišķā failā raksta pusmiljonu rindiņu "1". Patiesībā darba apjoms ir tāds pats kā iepriekšējā programmā. Bet laika gaitā šeit tiek iegūts interesants efekts. Programma var darboties no 0,7 sekundēm līdz pat 7 sekundēm. Kāpēc tas notiek?

Tas ir saistīts ar faktu, ka tad, ja pavedienam nav nepieciešams CPU resurss, tas atbrīvo GIL, un šajā brīdī tas var mēģināt to iegūt, un vēl viens pavediens, kā arī galvenais pavediens. Tajā pašā laikā operētājsistēma, zinot, ka ir daudz kodolu, var visu pasliktināt, mēģinot sadalīt pavedienus starp kodoliem.

UPD: pašlaik programmā Python 3.2 ir uzlabota GIL ieviešana, kurā šī problēma ir daļēji atrisināta, jo īpaši tāpēc, ka katrs pavediens pēc kontroles zaudēšanas gaida īsu laiku pirms tā atkal var uzņemt GIL (ir laba prezentācija angļu valodā)

"Tātad jūs nevarat rakstīt efektīvas daudzpavedienu programmas Python?" Jūs jautājat. Nē, protams, ir izeja, un pat vairākas.

Daudzapstrādes lietojumprogrammas


Lai kaut kādā ziņā atrisinātu iepriekšējā punktā aprakstīto problēmu, Python ir modulis apakšprocess ... Mēs varam uzrakstīt programmu, kuru vēlamies izpildīt paralēlā pavedienā (patiesībā jau process). Un palaidiet to vienā vai vairākos pavedienos citā programmā. Tas patiešām paātrinātu mūsu programmu, jo GIL palaidējā izveidotie pavedieni neuzņemas, bet tikai gaida, līdz darbības process tiks pārtraukts. Tomēr šai metodei ir daudz problēmu. Galvenā problēma ir tā, ka kļūst grūti pārsūtīt datus starp procesiem. Jums būtu kaut kā jāserializē objekti, jāveido saziņa, izmantojot PIPE vai citus rīkus, taču tas viss neizbēgami rada izmaksas, un kods kļūst grūti saprotams.

Šeit mums var palīdzēt cita pieeja. Python ir daudzapstrādes modulis ... Funkcionalitātes ziņā šis modulis atgādina vītņošana ... Piemēram, procesus var izveidot tādā pašā veidā no parastajām funkcijām. Metodes darbam ar procesiem ir gandrīz tādas pašas kā pavedieniem no vītņošanas moduļa. Bet ir ierasts izmantot citus rīkus, lai sinhronizētu procesus un apmainītos ar datiem. Mēs runājam par rindām (rinda) un caurulēm (caurule). Tomēr šeit ir arī slēdzeņu, notikumu un semaforu analogi, kas bija pavedieni.

Turklāt daudzapstrādes modulim ir mehānisms darbam ar koplietojamo atmiņu. Šim nolūkam modulim ir mainīgā (vērtība) un masīva (masīva) klases, kuras var “koplietot” starp procesiem. Lai ērtāk strādātu ar koplietojamiem mainīgajiem, varat izmantot pārvaldnieku klases. Tie ir elastīgāki un vieglāk lietojami, bet lēnāki. Jāatzīmē, ka ir jauka iespēja izveidot kopējus tipus no moduļa ctypes, izmantojot moduli multiprocessing.sharedctypes.

Arī daudzapstrādes modulī ir mehānisms procesu kopumu izveidošanai. Šo mehānismu ir ļoti ērti izmantot, lai ieviestu Meistara-Strādnieka modeli vai īstenotu paralēlu karti (kas savā ziņā ir īpašs Meistara-Darbinieka gadījums).

No galvenajām problēmām, kas saistītas ar darbu ar daudzapstrādes moduli, ir vērts atzīmēt šī moduļa relatīvo platformas atkarību. Tā kā darbs ar procesiem dažādās operētājsistēmās ir organizēts atšķirīgi, kodam ir noteikti daži ierobežojumi. Piemēram, operētājsistēmai Windows nav dakšu mehānisma, tāpēc procesa atdalīšanas punkts ir jāiesaiņo:

ja __name__ == "__main__":


Tomēr šis dizains jau ir laba forma.

Kas vēl...


Ir arī citas bibliotēkas un pieejas paralēlu lietojumprogrammu rakstīšanai Python. Piemēram, varat izmantot Hadoop + Python vai dažādas Python MPI implementācijas (pyMPI, mpi4py). Jūs pat varat izmantot esošo C ++ vai Fortran bibliotēku iesaiņojumus. Šeit varētu minēt tādas sistēmas / bibliotēkas kā Pyro, Twisted, Tornado un daudzas citas. Bet tas viss jau ir ārpus šī raksta darbības jomas.

Ja jums patika mans stils, tad nākamajā rakstā es mēģināšu jums pastāstīt, kā PLY rakstīt vienkāršus tulkus un kādam nolūkam tos var izmantot.

10. nodaļa.

Daudzpavedienu lietojumprogrammas

Daudzuzdevumu veikšana mūsdienu operētājsistēmās tiek uzskatīta par pašsaprotamu [ Pirms Apple OS X parādīšanās Macintosh datoros nebija modernu daudzuzdevumu operētājsistēmu. Ir ļoti grūti pareizi izstrādāt operētājsistēmu ar pilnvērtīgu daudzuzdevumu veikšanu, tāpēc OS X bija jābalstās uz Unix sistēmu.]. Lietotājs sagaida, ka, vienlaikus palaižot teksta redaktoru un pasta klientu, šīs programmas nebūs pretrunā, un, saņemot e-pastu, redaktors nepārtrauks darbu. Palaižot vairākas programmas vienlaikus, operētājsistēma ātri pārslēdzas starp programmām, pēc kārtas nodrošinot tām procesoru (ja vien, protams, datorā nav instalēti vairāki procesori). Rezultātā, ilūzija vienlaikus darbinot vairākas programmas, jo pat labākais mašīnrakstītājs (un ātrākais interneta savienojums) nespēj sekot līdzi mūsdienīgam procesoram.

Daudzvirzienu savā ziņā var uzskatīt par nākamo daudzuzdevumu līmeni: tā vietā, lai pārslēgtos starp dažādiem programmas, operētājsistēma pārslēdzas starp vienas un tās pašas programmas daļām. Piemēram, vairāku pavedienu e-pasta klients ļauj saņemt jaunas e-pasta ziņas, lasot vai rakstot jaunas ziņas. Mūsdienās daudzi pavedieni tiek uzskatīti par pašsaprotamu arī daudziem lietotājiem.

VB nekad nav bijis parastā daudzpavedienu atbalsta. Tiesa, viena no tās šķirnēm parādījās VB5 - sadarbības straumēšanas modelis(dzīvokļa vītņošana). Kā redzēsit drīz, sadarbības modelis programmētājam sniedz dažas priekšrocības, ko sniedz daudzpavedieni, taču tas pilnībā neizmanto visas funkcijas. Agrāk vai vēlāk jums ir jāpāriet no mācību mašīnas uz īstu, un VB .NET kļuva par pirmo VB versiju, kas atbalsta bezmaksas daudzšķiedru modeli.

Tomēr daudzpavedieni nav viena no funkcijām, kas ir viegli realizējamas programmēšanas valodās un viegli apgūstamas programmētājiem. Kāpēc?

Tā kā daudzšķiedru lietojumprogrammās var rasties ļoti viltīgas kļūdas, kas parādās un pazūd neparedzami (un šādas kļūdas ir visgrūtāk atkļūdot).

Godīgi brīdinot: daudzpavedieni ir viena no grūtākajām programmēšanas jomām. Mazākā neuzmanība noved pie nenotveramu kļūdu parādīšanās, kuru labošanai nepieciešamas astronomiskas summas. Šī iemesla dēļ šajā nodaļā ir daudz slikti piemēri - mēs tos apzināti rakstījām tā, lai parādītu izplatītas kļūdas. Šī ir drošākā pieeja, lai apgūtu daudzpavedienu programmēšanu: jums jāspēj pamanīt iespējamās problēmas, kad šķiet, ka viss no pirmā acu uzmetiena darbojas labi, un jāzina, kā tās atrisināt. Ja vēlaties izmantot vairāku pavedienu programmēšanas paņēmienus, bez tā nevar iztikt.

Šī nodaļa ieliks stabilu pamatu turpmākajam patstāvīgam darbam, taču mēs nevarēsim aprakstīt daudzpavedienu programmēšanu visos sarežģītības veidos - tikai drukātā dokumentācija par Threading nosaukumvietas klasēm aizņem vairāk nekā 100 lappuses. Ja vēlaties apgūt daudzpavedienu programmēšanu augstākā līmenī, skatiet specializētās grāmatas.

Bet neatkarīgi no tā, cik bīstama ir daudzpavedienu programmēšana, tā ir nepieciešama dažu problēmu profesionālam risinājumam. Ja jūsu programmas vajadzības gadījumā neizmanto daudzpavedienu, lietotāji kļūs ļoti neapmierināti un dos priekšroku citam produktam. Piemēram, tikai populārās e-pasta programmas Eudora ceturtajā versijā parādījās daudzpavedienu iespējas, bez kurām nav iespējams iedomāties nevienu mūsdienu e-pasta programmu. Līdz brīdim, kad Eudora ieviesa vairāku pavedienu atbalstu, daudzi lietotāji (tostarp viens no šīs grāmatas autoriem) bija pārgājuši uz citiem produktiem.

Visbeidzot, .NET programmā vienas vītnes programmas vienkārši nepastāv. Viss.NET programmas ir daudzpavedienu, jo atkritumu savācējs darbojas kā zemas prioritātes fona process. Kā parādīts zemāk, nopietnai grafiskai programmēšanai .NET, pareiza vītņošana var palīdzēt novērst grafiskā interfeisa bloķēšanu, kad programma veic ilgstošas ​​darbības.

Iepazīstinām ar daudzpavedienu

Katra programma darbojas noteiktā veidā konteksts, aprakstot koda un datu sadalījumu atmiņā. Saglabājot kontekstu, faktiski tiek saglabāts programmas plūsmas stāvoklis, kas ļauj to atjaunot nākotnē un turpināt programmas izpildi.

Konteksta saglabāšana ir saistīta ar laika un atmiņas izmaksām. Operētājsistēma atceras programmas pavediena stāvokli un pārsūta vadību uz citu pavedienu. Kad programma vēlas turpināt apturētā pavediena izpildi, ir jāatjauno saglabātais konteksts, kas aizņem vēl ilgāku laiku. Tāpēc daudzpavedienu drīkst izmantot tikai tad, ja ieguvumi kompensē visas izmaksas. Daži tipiski piemēri ir uzskaitīti zemāk.

  • Programmas funkcionalitāte ir skaidri un dabiski sadalīta vairākās neviendabīgās darbībās, piemēram, e-pasta saņemšanas un jaunu ziņojumu sagatavošanas piemērā.
  • Programma veic garus un sarežģītus aprēķinus, un jūs nevēlaties, lai grafiskais interfeiss tiktu bloķēts visu aprēķinu laikā.
  • Programma darbojas daudzprocesoru datorā ar operētājsistēmu, kas atbalsta vairāku procesoru izmantošanu (ja vien aktīvo pavedienu skaits nepārsniedz procesoru skaitu, paralēla izpilde praktiski nerada izmaksas, kas saistītas ar pavedienu pārslēgšanu).

Pirms pārejam pie daudzpavedienu programmu mehānikas, jānorāda viens apstāklis, kas bieži vien rada neskaidrības iesācēju vidū daudzšķiedru programmēšanas jomā.

Programmas plūsmā tiek izpildīta procedūra, nevis objekts.

Grūti pateikt, ko nozīmē izteiciens "objekts darbojas", taču viens no autoriem bieži pasniedz seminārus par daudzpavedienu programmēšanu un šis jautājums tiek uzdots biežāk nekā citi. Varbūt kāds domā, ka programmas pavediena darbs sākas ar aicinājumu uz klases jauno metodi, pēc kura pavediens apstrādā visus ziņojumus, kas nodoti attiecīgajam objektam. Šādas reprezentācijas absolūti ir nepareizi. Vienā objektā var būt vairāki pavedieni, kas izpilda dažādas (un dažreiz pat vienādas) metodes, savukārt objekta ziņojumus pārraida un saņem vairāki dažādi pavedieni (starp citu, tas ir viens no iemesliem, kas sarežģī vairāku pavedienu programmēšanu: lai atkļūdotu programmu, jums jānoskaidro, kurš pavediens konkrētajā brīdī veic šo vai citu procedūru!).

Tā kā pavedieni tiek veidoti no objektu metodēm, pats objekts parasti tiek izveidots pirms pavediena. Pēc veiksmīgas objekta izveides programma izveido pavedienu, nododot tam objekta metodes adresi un tikai pēc tam dod pavēli sākt pavediena izpildi. Procedūra, kurai pavediens tika izveidots, tāpat kā visas procedūras, var izveidot jaunus objektus, veikt darbības ar esošajiem objektiem un izsaukt citas procedūras un funkcijas, kas ietilpst tās darbības jomā.

Parastās nodarbību metodes var izpildīt arī programmu pavedienos. Šajā gadījumā arī paturiet prātā vēl vienu svarīgu apstākli: pavediens beidzas ar izeju no procedūras, kurai tā tika izveidota. Parastā programmas plūsmas pārtraukšana nav iespējama, kamēr netiek izieta procedūra.

Vītnes var pārtraukt ne tikai dabiski, bet arī neparasti. Tas parasti nav ieteicams. Plašāku informāciju skatiet sadaļā Straumju pārtraukšana un pārtraukšana.

Galvenās .NET funkcijas, kas saistītas ar programmatisko pavedienu izmantošanu, ir koncentrētas Threading nosaukumvietā. Tāpēc lielākajai daļai daudzpavedienu programmu vajadzētu sākt ar šādu rindu:

Importēšanas sistēma

Nosaukuma vietas importēšana atvieglo programmas rakstīšanu un iespējo IntelliSense tehnoloģiju.

Tieša plūsmu saistība ar procedūrām liecina, ka šajā attēlā delegāti(sk. 6. nodaļu). Konkrētāk, Threading nosaukumvieta ietver ThreadStart delegātu, ko parasti izmanto, startējot programmas pavedienus. Šī delegāta izmantošanas sintakse izskatās šādi:

Publiskā delegāta apakšvītneStart ()

Kodam, kas izsaukts ar ThreadStart delegātu, nedrīkst būt parametru vai atgriešanas vērtības, tāpēc pavedienus nevar izveidot funkcijām (kas atgriež vērtību) un procedūrām ar parametriem. Lai pārsūtītu informāciju no straumes, jums arī jāmeklē alternatīvi līdzekļi, jo izpildītās metodes neatgriež vērtības un nevar izmantot pārsūtīšanu ar atsauci. Piemēram, ja ThreadMethod ir WilluseThread klasē, tad ThreadMethod var sazināties ar informāciju, mainot WillUseThread klases gadījumu īpašības.

Lietojumprogrammu domēni

.NET pavedieni darbojas tā dēvētajos lietojumprogrammu domēnos, kas dokumentācijā ir definēti kā "smilšu kaste, kurā darbojas lietojumprogramma". Lietojumprogrammas domēnu var uzskatīt par vieglu Win32 procesu versiju; viens Win32 process var saturēt vairākus lietojumprogrammu domēnus. Galvenā atšķirība starp lietojumprogrammu domēniem un procesiem ir tāda, ka Win32 procesam ir sava adrešu telpa (dokumentācijā lietojumprogrammu domēni tiek salīdzināti arī ar loģiskiem procesiem, kas darbojas fiziskā procesā). NET sistēmā visu atmiņas pārvaldību veic izpildlaiks, tāpēc vienā Win32 procesā var darboties vairāki lietojumprogrammu domēni. Viena no šīs shēmas priekšrocībām ir lietojumprogrammu uzlabotās mērogošanas iespējas. Rīki darbam ar lietojumprogrammu domēniem ir klasē AppDomain. Mēs iesakām izpētīt šīs klases dokumentāciju. Ar tās palīdzību jūs varat iegūt informāciju par vidi, kurā darbojas jūsu programma. Jo īpaši AppDomain klase tiek izmantota, veicot pārdomas par .NET sistēmas klasēm. Šajā programmā ir uzskaitīti ielādētie mezgli.

Importa sistēma. Atspoguļošana

Moduļa modulis

Sub Main ()

Dim theDomain kā AppDomain

theDomain = AppDomain.CurrentDomain

Dim Asamblejas () Kā

Asamblejas = theDomain.GetAssemblies

Dim un montāžaxAs

Katrai montāžai mezglos

Console.WriteLinetanAssemble.Full Name) Tālāk

Console.ReadLine ()

Beigu apakš

Beigu modulis

Straumju veidošana

Sāksim ar elementāru piemēru. Pieņemsim, ka vēlaties palaist procedūru atsevišķā pavedienā, kas samazina skaitītāja vērtību bezgalīgā ciklā. Procedūra ir definēta kā daļa no klases:

Publiskā klase WillUseThreads

Public SubtractFromCounter ()

Dim skaitīt kā vesels skaitlis

Skaitīt patieso skaitu - = 1

Console.WriteLlne ("Esmu citā pavedienā un skaitītājs ="

& saskaitīt)

Cilpa

Beigu apakš

Beigt klasi

Tā kā nosacījums Do cilpa vienmēr ir patiess, jūs varētu domāt, ka nekas netraucēs SubtractFromCounter procedūrai. Tomēr daudzpavedienu lietojumprogrammā tas ne vienmēr notiek.

Šajā fragmentā ir parādīta apakšvienības procedūra, kas sāk pavedienu, un komanda Imports:

Opcija Stingri importa sistēmā. Vītņu moduļa modulis

Sub Main ()

1 Dim myTest kā jauns WillUseThreads ()

2 Dim bThreadStart kā jauns ThreadStart (AddressOf _

myTest.SubtractFromCounter)

3 Dim bThread kā jauna pavediens (bThreadStart)

4 collu bThread.Start ()

Dim i As Integer

5 Dariet, kamēr ir taisnība

Console.WriteLine ("Galvenajā pavedienā un skaits ir" & i) i + = 1

Cilpa

Beigu apakš

Beigu modulis

Apskatīsim vissvarīgākos punktus pēc kārtas. Pirmkārt, Sub Man n procedūra vienmēr darbojas galvenā straume(galvenais pavediens). .NET programmās vienmēr darbojas vismaz divi pavedieni: galvenais pavediens un atkritumu savākšanas pavediens. 1. rindā tiek izveidots jauns pārbaudes klases eksemplārs. 2. rindā mēs izveidojam ThreadStart delegātu un nododam SubtractFromCounter procedūras adresi testa klases instancē, kas izveidota 1. rindā (šo procedūru sauc bez parametriem). LabiImportējot Threading nosaukumvietu, garo nosaukumu var izlaist. Jaunais pavedienu objekts tiek izveidots 3. rindā. Zvanot uz Thread klases konstruktoram, ievērojiet ThreadStart delegāta aiziešanu. Daži programmētāji dod priekšroku šo divu rindu savienošanai vienā loģiskā rindā:

Dim bThread kā jauna pavediens (New ThreadStarttAddressOf _

myTest.SubtractFromCounter))

Visbeidzot, 4. rinda "sāk" pavedienu, izsaucot ThreadStart delegātam izveidotās Thread instances Start metodi. Izsaucot šo metodi, mēs sakām operētājsistēmai, ka atņemšanas procedūrai vajadzētu darboties atsevišķā pavedienā.

Vārds "sākas" iepriekšējā rindkopā ir iekļauts pēdiņās, jo šī ir viena no daudzdaļīgās programmēšanas dīvainībām: izsaukšana Sākt faktiski nesāk pavedienu! Tas tikai norāda operētājsistēmai ieplānot norādītā pavediena palaišanu, taču programma nevar tieši sākt. Jūs nevarēsit sākt pavedienu izpildi patstāvīgi, jo operētājsistēma vienmēr kontrolē pavedienu izpildi. Vēlākā sadaļā jūs uzzināsit, kā izmantot prioritāti, lai operētājsistēma ātrāk sāktu pavedienu.

Att. 10.1 parādīts piemērs tam, kas var notikt pēc programmas palaišanas un pēc tam tās pārtraukšanas ar taustiņu Ctrl + Break. Mūsu gadījumā jauns pavediens sākās tikai pēc tam, kad galvenā pavediena skaitītājs palielinājās līdz 341!

Rīsi. 10.1. Vienkāršs vairāku pavedienu programmatūras izpildlaiks

Ja programma darbojas ilgāku laiku, rezultāts izskatīsies līdzīgs attēlā redzamajam. 10.2. Mēs redzam, ka jūstekošā pavediena pabeigšana tiek apturēta un vadība atkal tiek pārnesta uz galveno pavedienu. Šajā gadījumā ir izpausme priekšlaicīga vairāku pavedienu sagriešana laika šķēlēs.Šī biedējošā termina nozīme ir izskaidrota zemāk.

Rīsi. 10.2. Pārslēgšanās starp pavedieniem vienkāršā daudzpavedienu programmā

Pārtraucot pavedienus un pārnesot vadību uz citiem pavedieniem, operētājsistēma izmanto iepriekšējas daudzpavedienu principu, izmantojot laika sagriešanu. Laika kvantēšana atrisina arī vienu no bieži sastopamajām problēmām, kas radušās iepriekš daudzšķiedru programmās - viens pavediens aizņem visu CPU laiku un nav zemāks par citu pavedienu kontroli (parasti tas notiek intensīvos ciklos, piemēram, iepriekš). Lai novērstu ekskluzīvu CPU nolaupīšanu, jūsu pavedieniem laiku pa laikam jāpārnes kontrole uz citiem pavedieniem. Ja programma izrādās "bezsamaņā", ir vēl viens, nedaudz mazāk vēlams risinājums: operētājsistēma vienmēr priekšplānā palaiž pavedienu neatkarīgi no tā prioritātes līmeņa, lai katram sistēmas pavedienam būtu piekļuve procesoram.

Tā kā visu Windows versiju, kurās darbojas .NET, kvantēšanas shēmām katram pavedienam ir piešķirta minimālā laika šķēle, .NET programmēšanā problēmas ar CPU ekskluzīvām satveršanām nav tik nopietnas. No otras puses, ja .NET ietvars kādreiz tiks pielāgots citām sistēmām, tas var mainīties.

Ja pirms izsaukšanas Sākt savā programmā iekļausim šādu rindu, tad pat pavedieni ar zemāko prioritāti iegūs daļu no CPU laika:

bThread.Priority = ThreadPriority.Highest

Rīsi. 10.3. Vītne ar augstāko prioritāti parasti sākas ātrāk

Rīsi. 10.4. Procesors ir paredzēts arī zemākas prioritātes pavedieniem

Komanda piešķir maksimālo prioritāti jaunajam pavedienam un samazina galvenā pavediena prioritāti. Att. 10.3 redzams, ka jaunais pavediens sāk darboties ātrāk nekā iepriekš, bet, kā parādīts attēlā. 10.4., Galvenais pavediens arī saņem kontrolislinkums (kaut arī ļoti īsu laiku un tikai pēc ilgstoša plūsmas darba ar atņemšanu). Palaižot programmu savos datoros, jūs iegūsit līdzīgus rezultātus, kā parādīts attēlā. 10.3 un 10.4, taču mūsu sistēmu atšķirību dēļ precīzas atbilstības nebūs.

ThreadPrlority uzskaitītais veids ietver vērtības pieciem prioritātes līmeņiem:

VītnePrioritāte. Augstākā

ThreadPriority.AboveNormal

ThreadPrlority. Normāli

ThreadPriority.BelowNormal

VītnePrioritāte. Zemākais

Pievienošanās metode

Dažreiz programmas pavediens ir jāpārtrauc, līdz beidzas cits pavediens. Pieņemsim, ka vēlaties apturēt 1. pavedienu, līdz 2. pavediens pabeidz aprēķināšanu. Priekš šī no straumes 1 plūsma 2. tiek saukta metode Join. Citiem vārdiem sakot, komanda

pavediens 2. Pievienojieties ()

aptur pašreizējo pavedienu un gaida, līdz pavediens 2. Pabeigts 1. pavediens bloķēts stāvoklis.

Ja pievienojat 1. straumi 2. straumei, izmantojot metodi Pievienoties, operētājsistēma automātiski sāks 1. straumi pēc 2. straumes. Ņemiet vērā, ka palaišanas process ir nenoteikts: nav iespējams precīzi pateikt, cik ilgi pēc 2. pavediena beigām sāks darboties 1. pavediens. Ir vēl viena Join versija, kas atgriež Būla vērtību:

pavediens 2. Pievienoties (vesels skaitlis)

Šī metode vai nu gaida 2. pavediena pabeigšanu, vai arī pēc noteiktā laika intervāla atbloķē 1. pavedienu, kā rezultātā operētājsistēmas plānotājs pavedienam atkal piešķir CPU laiku. Metode atgriež True, ja 2. pavediens beidzas pirms norādītā taimauta intervāla beigām, un False citādi.

Atcerieties pamatnoteikumu: neatkarīgi no tā, vai 2. pavediens ir pabeigts vai iestājās noildze, jūs nevarat kontrolēt, kad 1. pavediens ir aktivizēts.

Pavedienu nosaukumi, CurrentThread un ThreadState

Atribūts Thread.CurrentThread atgriež atsauci uz pavedienu objektu, kas pašlaik tiek izpildīts.

Lai gan ir pieejams lielisks pavedienu logs daudzpavedienu lietojumprogrammu atkļūdošanai VB .NET, kas ir aprakstīts zemāk, mums ļoti bieži palīdzēja komanda

MsgBox (Thread.CurrentThread.Name)

Bieži izrādījās, ka kods tiek izpildīts pilnīgi citā pavedienā, no kura tas bija jāizpilda.

Atgādinām, ka termins "programmu plūsmu nenoteikta plānošana" nozīmē ļoti vienkāršu lietu: programmētāja rīcībā praktiski nav līdzekļu, lai ietekmētu plānotāja darbu. Šī iemesla dēļ programmas bieži izmanto ThreadState rekvizītu, lai atgrieztu informāciju par pavediena pašreizējo stāvokli.

Plūsmu logs

Visual Studio .NET pavedienu logs ir nenovērtējams daudzpavedienu programmu atkļūdošanā. To aktivizē ar atkļūdošanas> Windows apakšizvēlnes komandu pārtraukuma režīmā. Pieņemsim, ka jūs piešķīrāt nosaukumu bThread pavedienam ar šādu komandu:

bThread.Name = "Atņemot pavedienu"

Plūsmu loga aptuvenais skats pēc programmas pārtraukšanas ar taustiņu kombināciju Ctrl + Break (vai citā veidā) ir parādīts attēlā. 10.5.

Rīsi. 10.5. Plūsmu logs

Bultiņa pirmajā kolonnā iezīmē aktīvo pavedienu, ko atgriež Thread.CurrentThread rekvizīts. Slejā ID ir ietverti ciparu pavedienu ID. Nākamajā slejā ir uzskaitīti straumju nosaukumi (ja tie ir piešķirti). Kolonna Atrašanās vieta norāda izpildāmo procedūru (piemēram, konsoles klases WriteLine procedūra 10.5. Attēlā). Pārējās kolonnās ir informācija par prioritārajiem un apturētajiem pavedieniem (sk. Nākamo sadaļu).

Pavedienu logs (nevis operētājsistēma!) Ļauj kontrolēt savas programmas pavedienus, izmantojot konteksta izvēlnes. Piemēram, pašreizējo pavedienu var apturēt, ar peles labo pogu noklikšķinot uz atbilstošās rindas un izvēloties komandu Iesaldēt (vēlāk apturēto pavedienu var atsākt). Atkļūdošanas laikā bieži tiek izmantoti pārtraukšanas pavedieni, lai novērstu to, ka nepareizi strādājošs pavediens netraucē lietojumprogrammai. Turklāt straumju logs ļauj aktivizēt citu (neapturētu) straumi; lai to izdarītu, ar peles labo pogu noklikšķiniet uz nepieciešamās rindas un konteksta izvēlnē atlasiet komandu Pārslēgties uz pavedienu (vai vienkārši veiciet dubultklikšķi uz pavedienu līnijas). Kā tiks parādīts turpmāk, tas ir ļoti noderīgi, lai diagnosticētu iespējamos strupceļus.

Straumes apturēšana

Īslaicīgi neizmantotās plūsmas var pārsūtīt pasīvā stāvoklī, izmantojot Slеer metodi. Arī pasīva straume tiek uzskatīta par bloķētu. Protams, kad pavediens tiek novietots pasīvā stāvoklī, pārējiem pavedieniem būs vairāk procesora resursu. Slеer metodes standarta sintakse ir šāda: Thread.Sleep (interval_in_milliseconds)

Miega režīma zvana rezultātā aktīvais pavediens kļūst pasīvs vismaz uz noteiktu skaitu milisekunžu (tomēr netiek garantēta aktivizēšana tūlīt pēc norādītā intervāla beigām). Lūdzu, ņemiet vērā: izsaucot metodi, atsauce uz konkrētu pavedienu netiek nodota - miega metode tiek izsaukta tikai aktīvajam pavedienam.

Vēl viena miega versija liek pašreizējam pavedienam atteikties no pārējā piešķirtā CPU laika:

Vītne. Miega režīms (0)

Nākamā opcija pašreizējo pavedienu neierobežotu laiku novieto pasīvā stāvoklī (aktivizēšana notiek tikai tad, ja zvanāt uz Pārtraukt):

Thread.SlER (Timeout.Infinite)

Tā kā pasīvos pavedienus (pat ar neierobežotu taimautu) var pārtraukt, izmantojot pārtraukšanas metodi, kas noved pie ThreadlnterruptExcepti izņēmuma gadījuma, Slayer zvans vienmēr ir iekļauts Try-Catch blokā, kā parādīts šādā fragmentā:

Izmēģiniet

Vītne. Miega režīms (200)

"Vītnes pasīvais stāvoklis ir pārtraukts

Catch e kā izņēmums

"Citi izņēmumi

Beigt Izmēģiniet

Katra .NET programma darbojas programmas pavedienā, tāpēc miega metode tiek izmantota arī programmu apturēšanai (ja programma neimportē Threadipg nosaukumvietu, jāizmanto pilnībā kvalificēts nosaukums Threading.Thread. Sleep).

Programmas pavedienu pārtraukšana vai pārtraukšana

Vītne tiek automātiski pārtraukta, kad tiek izveidota ThreadStart delegāta norādītā metode, bet dažreiz metodei (un līdz ar to pavedienam) ir jābeidzas, kad rodas noteikti faktori. Šādos gadījumos straumes parasti pārbauda nosacīts mainīgais, atkarībā no stāvokļa, kurātiek pieņemts lēmums par avārijas izeju no straumes. Parasti Do-while cilpa ir iekļauta šajā procedūrā:

Sub ThreadedMethod ()

"Programmai ir jānodrošina līdzekļi aptaujai

"nosacīts mainīgais.

"Piemēram, nosacītu mainīgo var veidot kā īpašumu

Darīt, kamēr nosacījumsVariable = False And MoreWorkToDo

"Galvenais kods

Cilpas beigu apakšdaļa

Nosacītā mainīgā aptaujāšana prasa zināmu laiku. Pastāvīgu aptauju cikla stāvoklī izmantojiet tikai tad, ja gaidāt pavediena priekšlaicīgu pārtraukšanu.

Ja nosacījuma mainīgais ir jāpārbauda noteiktā vietā, izmantojiet komandu If-then kopā ar Exit Sub bezgalīgas cilpas ietvaros.

Piekļuve nosacītam mainīgajam ir jāsinhronizē tā, lai citu pavedienu iedarbība netraucētu tā normālu izmantošanu. Šī svarīgā tēma ir apskatīta sadaļā "Problēmu novēršana: sinhronizācija".

Diemžēl pasīvo (vai citādi bloķēto) pavedienu kods netiek izpildīts, tāpēc opcija ar nosacīta mainīgā aptauju viņiem nav piemērota. Šādā gadījumā izsauciet pārtraukšanas metodi objektam mainīgajam, kas satur atsauci uz vēlamo pavedienu.

Pārtraukšanas metodi var izsaukt tikai pavedienos gaidīšanas, miega vai pievienošanās stāvoklī. Ja izsaucat pārtraukumu pavedienam, kas atrodas kādā no uzskaitītajiem stāvokļiem, tad pēc kāda laika pavediens atkal sāks darboties, un izpildes vide pavedienā ierosinās ThreadlnterruptExcepti. Tas notiek pat tad, ja pavediens ir padarīts pasīvs uz nenoteiktu laiku, izsaucot Thread.Sleepdimeout. Bezgalīgs). Mēs sakām "pēc kāda laika", jo pavedienu plānošana nav noteicoša. ThreadlnterruptExcept uz izņēmumu tiek uztverta sadaļā Catch, kurā ir izejas kods no gaidīšanas stāvokļa. Tomēr sadaļai Catch nav jāpārtrauc pavediens pārtraukuma zvanā - pavediens izņēmumu apstrādā pēc saviem ieskatiem.

.NET sistēmā pārtraukšanas metodi var izsaukt pat atbloķētiem pavedieniem. Šajā gadījumā pavediens tiek pārtraukts tuvākajā bloķēšanā.

Vītņu apturēšana un nogalināšana

Threading nosaukumvietā ir citas metodes, kas pārtrauc parasto pavedienu:

  • Apturēt;
  • Pārtraukt.

Grūti pateikt, kāpēc .NET ietvēra atbalstu šīm metodēm - zvanot uz Suspend un Abort, programma, visticamāk, kļūs nestabila. Neviena no metodēm nepieļauj straumes normālu deinitializāciju. Turklāt, zvanot uz Apturēt vai Pārtraukt, nav iespējams paredzēt, kādā stāvoklī pavediens atstās objektus pēc apturēšanas vai pārtraukšanas.

Zvana pārtraukšana izsauc ThreadAbortException. Lai palīdzētu jums saprast, kāpēc šo dīvaino izņēmumu nevajadzētu apstrādāt programmās, šeit ir izvilkums no .NET SDK dokumentācijas:

“... Ja pavediens tiek iznīcināts, izsaucot pārtraukt, izpildlaiks rada ThreadAbortException. Šis ir īpašs izņēmuma veids, ko programma nevar aptvert. Kad tiek izmests šis izņēmums, izpildlaiks izpilda visus Beigu blokus pirms pavediena pārtraukšanas. Tā kā jebkura darbība var notikt Visbeidzot blokos, zvaniet Join, lai nodrošinātu straumes iznīcināšanu. "

Morāle: pārtraukt un apturēt nav ieteicams (un, ja jūs joprojām nevarat iztikt bez apturēšanas, atsāciet apturēto pavedienu, izmantojot Resume metodi). Jūs varat droši pārtraukt pavedienu, tikai aptaujājot sinhronizētu nosacījumu mainīgo vai izsaucot iepriekš aprakstīto pārtraukšanas metodi.

Fona pavedieni (dēmoni)

Daži pavedieni, kas darbojas fonā, automātiski pārtrauc darboties, kad apstājas citas programmas sastāvdaļas. Jo īpaši atkritumu savācējs darbojas vienā no fona pavedieniem. Fona pavedieni parasti tiek veidoti datu saņemšanai, taču tas tiek darīts tikai tad, ja citos pavedienos darbojas kods, kas var apstrādāt saņemtos datus. Sintakse: straumes nosaukums. IsBackGround = True

Ja lietojumprogrammā ir palikuši tikai fona pavedieni, lietojumprogramma tiks automātiski pārtraukta.

Nopietnāks piemērs: datu iegūšana no HTML koda

Mēs iesakām izmantot straumes tikai tad, ja programmas funkcionalitāte ir skaidri sadalīta vairākās darbībās. Labs piemērs ir HTML ieguves programma 9. nodaļā. Mūsu klase veic divas lietas: iegūst datus no Amazon un apstrādā tos. Tas ir ideāls piemērs situācijai, kad daudzpavedienu programmēšana ir patiešām piemērota. Mēs izveidojam klases vairākām dažādām grāmatām un pēc tam parsējam datus dažādās plūsmās. Jaunas grāmatas izveidošana katrai grāmatai palielina programmas efektivitāti, jo, kamēr viens pavediens saņem datus (kas var prasīt gaidīšanu Amazon serverī), cits pavediens būs aizņemts ar jau saņemto datu apstrādi.

Šīs programmas vairāku pavedienu versija darbojas efektīvāk nekā viena pavediena versija tikai datorā ar vairākiem procesoriem vai ja papildu datu saņemšanu var efektīvi apvienot ar to analīzi.

Kā minēts iepriekš, pavedienos var palaist tikai tās procedūras, kurām nav parametru, tāpēc jums būs jāveic nelielas izmaiņas programmā. Tālāk ir sniegta pamata procedūra, kas pārrakstīta, lai izslēgtu parametrus:

Publiskais apakšmeklēšanas rangs ()

m_Rank = ScrapeAmazon ()

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

Beigu apakš

Tā kā mēs nevarēsim izmantot kombinēto lauku informācijas glabāšanai un izgūšanai (daudzpavedienu programmu rakstīšana ar grafisko interfeisu ir apskatīta šīs nodaļas pēdējā sadaļā), programma saglabā četru grāmatu datus masīvā, definīcija sākas šādi:

Grāmatas aptumšošana (3.1) kā grāmatas grāmata (0.0) = "1893115992"

theBook (0.l) = "Programmēšana VB .NET" "Utt.

Tajā pašā ciklā, kurā tiek izveidoti AmazonRanker objekti, tiek izveidotas četras plūsmas:

Ja i = 0 līdz 3

Izmēģiniet

theRanker = Jauns AmazonRanker (TheBook (i.0). TheBookd.1))

aThreadStart = Jauna ThreadStar (AddressRofer.FindRan ()

aThread = Jauna pavediens (aThreadStart)

aThread.Name = grāmata (i.l)

aThread.Start () Catch e kā izņēmums

Console.WriteLine (e.Message)

Beigt Izmēģiniet

Nākamais

Zemāk ir pilns programmas teksts:

Opcija Stingri importēšanas sistēmā.IO Imports System.Net

Importēšanas sistēma

Moduļa modulis

Sub Main ()

Grāmatas aptumšošana (3.1) kā virkne

grāmata (0,0) = "1893115992"

theBook (0.l) = "VB .NET programmēšana"

grāmata (l.0) = "1893115291"

theBook (l.l) = "Datu bāzes programmēšana VB .NET"

grāmata (2,0) = "1893115623"

theBook (2.1) = "Programmētāja ievads C #."

grāmata (3.0) = "1893115593"

theBook (3.1) = "Uzlieciet .Net platformu"

Dim i As Integer

Dim theRanker kā = AmazonRanker

Dim aThreadStart kā Threading.ThreadStart

Dim aThread kā Threading.Thread

Ja i = 0 līdz 3

Izmēģiniet

theRanker = Jauna AmazonRankerttheBook (i.0). grāmata (i.1))

aThreadStart = Jauna ThreadStart (AddressRofer. FindRank)

aThread = Jauna pavediens (aThreadStart)

aThread.Name = grāmata (i.l)

aThread.Start ()

Catch e kā izņēmums

Console.WriteLlnete.Message)

Beigt Mēģināt tālāk

Console.ReadLine ()

Beigu apakš

Beigu modulis

Sabiedriskās klases AmazonRanker

Privāts m_URL kā virkne

Privāts m_Rank kā vesels skaitlis

Privāts m_Name As String

Public Sub New (ByVal ISBN kā virkne. ByVal theName kā virkne)

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

m_Name = theName End Sub

Public Sub FindRank () m_Rank = ScrapeAmazon ()

Console.Writeline ("& m_Name &" rangs ir "

& GetRank) Beigu apakš

Publisks tikai lasāms īpašums GetRank () kā virkne

Ja m_Rank<>0 Tad

Atgriezties CStr (m_Rank) Cits

"Problēmas

Beigas Ja

Beigt Iegūt

Beigu īpašums

Publisks tikai lasāms īpašums GetName () kā virkne

Atgrieziet m_Name

Beigt Iegūt

Beigu īpašums

Privāta funkcija ScrapeAmazon () kā vesels skaitlis

Aptumšojiet URL kā jaunu Uri (m_URL)

Aptumšojiet pieprasījumu kā WebRequest

theRequest = WebRequest.Create (theURL)

Dim theResponse kā WebResponse

theResponse = theRequest.GetResponse

Dim aReader kā jauns StreamReader (theResponse.GetResponseStream ())

Izgaismojiet datus kā virkni

theData = aReader.ReadToEnd

Atgriešanās analīze (theData)

Nozveja E kā izņēmums

Console.WriteLine (E.Message)

Console.WriteLine (E.StackTrace)

Konsole. ReadLine ()

Beigt Izmēģināt beigu funkciju

Privāto funkciju analīze (ByVal theData As String) kā vesels skaitlis

Dim atrašanās vieta kā.Integera atrašanās vieta = theData.IndexOf (" Amazon.com

Pārdošanas rangs:") _

+ "Amazon.com pārdošanas rangs:". Garums

Aptumšota temperatūra kā virkne

Darīt līdz theData.Substring (Location.l) = "<" temp = temp

& theData.Substring (Location.l) Atrašanās vieta + = 1 cilpa

Atgriezt Clnt (temp)

Beigu funkcija

Beigt klasi

.NET un I / O nosaukumvietās parasti tiek izmantotas daudzpavedienu darbības, tāpēc .NET Framework bibliotēka tām nodrošina īpašas asinhronās metodes. Papildinformāciju par asinhrono metožu izmantošanu, rakstot daudzpavedienu programmas, skatiet HTTPWebRequest klases BeginGetResponse un EndGetResponse metodēs.

Galvenais apdraudējums (vispārīgi dati)

Līdz šim ir izskatīts vienīgais drošs pavedienu izmantošanas gadījums - mūsu straumes nemainīja vispārējos datus. Ja ļaujat mainīties vispārīgajiem datiem, iespējamās kļūdas sāk eksponenciāli vairoties, un kļūst daudz grūtāk no tām atbrīvoties programmai. No otras puses, ja jūs aizliedzat koplietoto datu modificēšanu ar dažādiem pavedieniem, daudzpavedienu .NET programmēšana diez vai atšķirsies no VB6 ierobežotajām iespējām.

Mēs piedāvājam jums nelielu programmu, kas parāda problēmas, kas rodas, neiedziļinoties nevajadzīgās detaļās. Šī programma simulē māju ar termostatu katrā telpā. Ja temperatūra ir par 5 grādiem pēc Fārenheita vai vairāk (aptuveni 2,77 grādi pēc Celsija) zemāka par mērķa temperatūru, mēs pasūtām apkures sistēmai paaugstināt temperatūru par 5 grādiem; pretējā gadījumā temperatūra paaugstinās tikai par 1 grādu. Ja pašreizējā temperatūra ir lielāka vai vienāda ar iestatīto, izmaiņas netiek veiktas. Temperatūras kontrole katrā telpā tiek veikta ar atsevišķu plūsmu ar 200 milisekundes aizkavi. Galvenais darbs tiek veikts ar šādu fragmentu:

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

Vītne. Miega režīms (200)

Kaklasaite kā ThreadlnterruptException

"Pasīvā gaidīšana ir pārtraukta

Catch e kā izņēmums

"Citi beigu izmēģinājuma izņēmumi

mHouse.HouseTemp + - 5 "utt.

Zemāk ir pilns programmas avota kods. Rezultāts ir parādīts attēlā. 10.6: temperatūra mājā ir sasniegusi 105 grādus pēc Fārenheita (40,5 grādi pēc Celsija)!

1 Opcija Stingri ieslēgta

2 Importēšanas sistēma

3 Moduļa modulis

4 Sub Main ()

5 Dim myHouse kā jauna māja (l0)

6 Konsole. ReadLine ()

7 End Sub

8 Beigu modulis

9 Sabiedriskās klases māja

10 Public Const MAX_TEMP kā vesels skaitlis = 75

11 Privāts mCurTemp kā vesels skaitlis = 55

12 Privāti mRooms () kā istaba

13 Public Sub New (ByVal numOfRooms kā vesels skaitlis)

14 ReDim mRooms (numOfRooms = 1)

15 Dim i As Integer

16 Dim aThreadStart kā Threading.ThreadStart

17 Dim aThread kā pavediens

18 Ja i = 0 Uz numOfRooms -1

19 Izmēģiniet

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

21 aThreadStart - Jauna ThreadStart (AddressOf _

mRooms (i). CheckTempInRoom)

22 aThread = Jauna pavediens (aThreadStart)

23 aVītne. Sākt ()

24 Nozveja E kā izņēmums

25 Console.WriteLine (E.StackTrace)

26 Beigas Mēģiniet

27 Tālāk

28 Beigu apakš

29 Public Property HouseTemp () Kā vesels skaitlis

trīsdesmit. gūt

31 Atgriezt mCurTemp

32 Beigt Iegūt

33 Iestatīt (ByVal vērtība kā vesels skaitlis)

34 mCurTemp = Vērtība 35 Beigu kopa

36 Gala īpašums

37 Beigu klase

38 Publiskās klases istaba

39 Privāts mCurTemp kā vesels skaitlis

40 Privāts mName As String

41 Privāts mHouse As House

42 Public Sub New (ByVal theHouse As House,

ByVal temp kā vesels skaitlis, ByVal roomName kā virkne)

43 mHouse = theHouse

44 mCurTemp = temp

45 mNosaukums = istabasNosaukums

46 Beigu apakš

47 Public Sub CheckTempInRoom ()

48 Temperatūras maiņa ()

49 Beigu apakš

50 privāta apakšmaiņa Temperatūra ()

51 Izmēģiniet

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

53 Vītne. Miega režīms (200)

54 mHouse.HouseTemp + - 5

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

56 ". Pašreizējā temperatūra ir" & mHouse.HouseTemp)

57. Elself mHouse.HouseTemp< mHouse.MAX_TEMP Then

58 Vītne. Miega režīms (200)

59 mHouse.HouseTemp + = 1

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

61 ". Pašreizējā temperatūra ir" & mHouse.HouseTemp)

62 Citādi

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

64 ". Pašreizējā temperatūra ir" & mHouse.HouseTemp)

65 "Nedariet neko, temperatūra ir normāla

66 Beigt Ja

67 Catch tae As ThreadlnterruptException

68 "Pasīvā gaidīšana ir pārtraukta

69 Noķert e kā izņēmumu

70 "Citi izņēmumi

71 Beigas Mēģiniet

72 Beigu apakš

73 Beigu klase

Rīsi. 10.6. Vairāku pavedienu jautājumi

Sub Main procedūra (4-7. Rindas) izveido "māju" ar desmit "istabām". Mājas klase nosaka maksimālo temperatūru 75 grādi pēc Fārenheita (aptuveni 24 grādi pēc Celsija). 13.-28. Līnija nosaka diezgan sarežģītu māju celtnieku. Līnijas 18-27 ir galvenais, lai izprastu programmu. 20. rindā tiek izveidots vēl viens telpas objekts, un atsauce uz mājas objektu tiek nodota konstruktoram, lai telpas objekts vajadzības gadījumā varētu uz to atsaukties. Līnijas 21-23 sāk desmit straumes, lai pielāgotu temperatūru katrā telpā. Telpas klase ir definēta 38.-73. Rindā. House coxpa atsaucetiek saglabāts Room klases konstruktora mainīgā mHouse (43. rinda). Kods temperatūras pārbaudei un regulēšanai (50.-66.rindas) izskatās vienkāršs un dabisks, taču, kā drīz redzēsiet, šis iespaids ir maldinošs! Ņemiet vērā, ka šis kods ir ietīts Try-Catch blokā, jo programma izmanto miega metodi.

Diez vai kāds piekristu dzīvot temperatūrā 105 grādi pēc Fārenheita (40,5 līdz 24 grādi pēc Celsija). Kas notika? Problēma ir saistīta ar šādu rindu:

Ja mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then

Un notiek sekojošais: pirmkārt, temperatūru pārbauda ar plūsmu 1. Viņš redz, ka temperatūra ir pārāk zema, un paaugstina to par 5 grādiem. Diemžēl pirms temperatūras paaugstināšanās 1. plūsma tiek pārtraukta un vadība tiek pārnesta uz 2. plūsmu. 2. straume pārbauda to pašu mainīgo vēl nav mainīts plūsma 1. Tādējādi plūsma 2 arī gatavojas paaugstināt temperatūru par 5 grādiem, bet tai nav laika to izdarīt un arī nonāk gaidīšanas stāvoklī. Process turpinās, līdz tiek aktivizēta 1. plūsma, un tiek pārvietota uz nākamo komandu - paaugstinot temperatūru par 5 grādiem. Palielinājums tiek atkārtots, kad tiek aktivizētas visas 10 plūsmas, un mājas iedzīvotājiem būs slikti.

Problēmas risinājums: sinhronizācija

Iepriekšējā programmā rodas situācija, kad programmas izlaide ir atkarīga no pavedienu izpildes secības. Lai atbrīvotos no tā, jums jāpārliecinās, ka komandām patīk

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

tiek pilnībā apstrādāts ar aktīvo pavedienu, pirms tas tiek pārtraukts. Šo īpašumu sauc atomu kauns - katram pavedienam bez pārtraukuma jāizpilda koda bloks kā atomu vienībai. Pavedienu plānotājs nevar pārtraukt komandu grupu, kas apvienota atomu blokā. Jebkurai daudzpavedienu programmēšanas valodai ir savi veidi, kā nodrošināt atomu. VB .NET vienkāršākais veids, kā izmantot komandu SyncLock, ir ievadīt objekta mainīgo, kad tas tiek izsaukts. Iepriekšējā piemērā veiciet nelielas izmaiņas ChangeTemperature procedūrā, un programma darbosies labi:

Privāta apakšmaiņa Temperatūra () SyncLock (mHouse)

Izmēģiniet

Ja mHouse.HouseTemp< mHouse.MAXJTEMP -5 Then

Vītne. Miega režīms (200)

mHouse.HouseTemp + = 5

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

". Pašreizējā temperatūra ir" & mHouse.HouseTemp)

Paši

mHouse.HouseTemp< mHouse. MAX_TEMP Then

Vītne. Miega režīms (200) mHouse.HouseTemp + = 1

Console.WriteLine ("Am in" & Me.mName & _ ". Pašreizējā temperatūra ir" & mHouse.HomeTemp) Cits

Console.WriteLineC "Es esmu" un Me.mName & _ ". Pašreizējā temperatūra ir" & mHouse.HouseTemp)

"Neko nedariet, temperatūra ir normāla

Beigt, ja ķeras pie kaklasaites kā pavediens pārtraukts Izņēmums

"Pasīvo gaidīšanu pārtrauca Catch e kā izņēmums

"Citi izņēmumi

Beigt Izmēģiniet

Beigt SyncLock

Beigu apakš

Bloka kods SyncLock tiek izpildīts atomu veidā. Piekļuve tai no visiem pārējiem pavedieniem tiks slēgta, līdz pirmais pavediens atbrīvos slēdzeni ar komandu Beigt SyncLock. Ja pavediens sinhronizētā blokā nonāk pasīvā gaidīšanas stāvoklī, slēdzene paliek, līdz pavediens tiek pārtraukts vai atsākts.

Pareiza SyncLock komandas izmantošana aizsargā jūsu programmas pavedienu. Diemžēl SyncLock pārmērīga izmantošana negatīvi ietekmē veiktspēju. Sinhronizējot kodu daudzpavedienu programmā, tās darba ātrums samazinās vairākas reizes. Sinhronizējiet tikai visvairāk nepieciešamo kodu un pēc iespējas ātrāk atlaidiet slēdzeni.

Pamata kolekciju klases ir nedrošas daudzpavedienu lietojumprogrammās, taču .NET Framework ietver pavedienu drošas vairuma kolekciju klases versijas. Šajās klasēs potenciāli bīstamo metožu kods ir iekļauts SyncLock blokos. Vītņu drošas kolekciju klašu versijas jāizmanto daudzpavedienu programmās visur, kur tiek apdraudēta datu integritāte.

Atliek pieminēt, ka nosacītie mainīgie ir viegli īstenojami, izmantojot komandu SyncLock. Lai to izdarītu, jums vienkārši jāsinhronizē rakstīšana ar kopējo Būla īpašumu, kas ir pieejams lasīšanai un rakstīšanai, kā tas tiek darīts šādā fragmentā:

Publiskas klases stāvoklis

Privāts koplietojams skapītis kā objekts = jauns objekts ()

Privāti koplietots mOK kā Būla koplietots

Īpašums TheConditionVariable () Kā Būla

gūt

Atgrieziet mOK

Beigt Iegūt

Iestatīt (ByVal vērtība kā Būla) SyncLock (skapītis)

mOK = vērtība

Beigt SyncLock

Beigu komplekts

Beigu īpašums

Beigt klasi

SyncLock komandu un monitoru klase

Komandas SyncLock izmantošana ietver dažus smalkumus, kas netika parādīti iepriekš minētajos vienkāršajos piemēros. Tātad sinhronizācijas objekta izvēlei ir ļoti svarīga loma. Mēģiniet palaist iepriekšējo programmu ar komandu SyncLock (Me), nevis SyncLock (mHouse). Temperatūra atkal paceļas virs sliekšņa!

Atcerieties, ka komanda SyncLock tiek sinhronizēta, izmantojot objekts, nodots kā parametrs, nevis koda fragments. Parametrs SyncLock darbojas kā durvis, lai piekļūtu sinhronizētajam fragmentam no citiem pavedieniem. Komanda SyncLock (Me) faktiski atver vairākas dažādas "durvis", no kā jūs centāties izvairīties no sinhronizācijas. Morāle:

Lai aizsargātu koplietotos datus daudzpavedienu lietojumprogrammā, komandai SyncLock ir jāsinhronizē viens objekts vienlaikus.

Tā kā sinhronizācija ir saistīta ar konkrētu objektu, dažās situācijās ir iespējams netīši bloķēt citus fragmentus. Pieņemsim, ka jums ir divas sinhronizētas metodes, pirmā un otrā, un abas metodes tiek sinhronizētas bigLock objektā. Kad 1. pavediens vispirms ievada metodi un tver bigLock, neviens pavediens nevarēs ievadīt otro metodi, jo piekļuve tai jau ir ierobežota līdz 1. pavedienam!

Komandas SyncLock funkcionalitāti var uzskatīt par monitora klases funkcionalitātes apakškopu. Monitoru klase ir ļoti pielāgojama, un to var izmantot, lai atrisinātu nebūtiskus sinhronizācijas uzdevumus. Komanda SyncLock ir aptuvens Moni tor klases Enter un Exi t analogu analogs:

Izmēģiniet

Monitor. Ievadiet (theObject) Visbeidzot

Monitor.Exit (theObject)

Beigt Izmēģiniet

Dažām standarta operācijām (mainīgā palielināšana / samazināšana, divu mainīgo satura apmaiņa) .NET Framework nodrošina klasi Interlocked, kuras metodes veic šīs darbības atomu līmenī. Izmantojot klasi Interlocked, šīs darbības ir daudz ātrākas nekā izmantojot SyncLock komandu.

Bloķēšana

Sinhronizācijas laikā slēdzene tiek iestatīta uz objektiem, nevis pavedieniem, tāpēc, lietojot savādāk bloķējamie objekti savādāk koda fragmenti programmās dažkārt rodas visai nebūtiskas kļūdas. Diemžēl daudzos gadījumos sinhronizācija ar vienu objektu vienkārši nav atļauta, jo tas pārāk bieži bloķēs pavedienus.

Apsveriet situāciju saslēdzoties(strupceļš) visvienkāršākajā formā. Iedomājieties divus programmētājus pie pusdienu galda. Diemžēl viņiem ir tikai viens nazis un viena dakša diviem. Pieņemot, ka ēst vajag gan nazi, gan dakšiņu, ir iespējamas divas situācijas:

  • Kāds programmētājs paspēj paķert nazi un dakšiņu un sāk ēst. Kad viņš ir pilns, viņš noliek vakariņas, un tad cits programmētājs var tās paņemt.
  • Viens programmētājs paņem nazi, bet otrs - dakšiņu. Ne viens, ne otrs nevar sākt ēst, ja vien otrs neatsakās no savas ierīces.

Daudzpavedienu programmā šo situāciju sauc savstarpēja bloķēšana. Abas metodes tiek sinhronizētas dažādos objektos. Vītne A uztver objektu 1 un ieiet programmas daļā, ko aizsargā šis objekts. Diemžēl, lai tas darbotos, tam ir nepieciešama piekļuve kodam, ko aizsargā cita sinhronizācijas bloķēšana ar citu sinhronizācijas objektu. Bet pirms tam ir laiks ievadīt fragmentu, ko sinhronizē cits objekts, straume B tajā iekļūst un uztver šo objektu. Tagad pavediens A nevar iekļūt otrajā fragmentā, pavediens B nevar iekļūt pirmajā fragmentā, un abi pavedieni ir lemti gaidīt bezgalīgi. Neviens pavediens nevar turpināt darboties, jo nepieciešamais objekts nekad netiks atbrīvots.

Strupceļu diagnostiku sarežģī fakts, ka tie var rasties salīdzinoši retos gadījumos. Tas viss ir atkarīgs no kārtības, kādā plānotājs tiem piešķir CPU laiku. Iespējams, ka vairumā gadījumu sinhronizācijas objekti tiks uztverti bez strupceļa.

Tālāk ir sniegta tikko aprakstītās strupceļa situācijas ieviešana. Pēc īsas diskusijas par būtiskākajiem punktiem mēs parādīsim, kā pavedienu logā noteikt strupceļa situāciju:

1 Opcija Stingri ieslēgta

2 Importēšanas sistēma

3 Moduļa modulis

4 Sub Main ()

5 Dim Tom kā jauns programmētājs ("Toms")

6 Dim Bobs kā jaunais programmētājs ("Bobs")

7 Dim aThreadStart as New ThreadStart (Tom.Eat adrese)

8 Dim aThread kā jauna pavediens (aThreadStart)

9 aThread.Name = "Toms"

10 Dim bThreadStart kā jauns pavediensStarttAddressOf Bob.Eat)

11 Dim bThread kā jauna pavediens (bThreadStart)

12 bThread.Name = "Bobs"

13 aVītne. Sākt ()

14 bVītne. Sākt ()

15 Beigu apakš

16 Beigu modulis

17 Publiskās klases dakša

18 Private Shared mForkAvaiTable As Būla = True

19 Private Shared mOwner As String = "Neviens"

20 privāts, tikai lasāms īpašums, kuram pieder Utensil () kā virkne

21 Iegūt

22 Atgrieziet mOwner

23 Beigt Iegūt

24 Gala īpašums

25 Public Sub GrabForktByVal a kā programmētājs)

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

"mēģinot satvert dakšiņu.")

27 Console.WriteLine (Me.OwnsUtensil & "ir dakša."). ...

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

29 Ja mForkAvailable then

30 a.HasFork = Taisnība

31 mĪpašnieks = a.ManName

32 mForkAvailable = Nepatiess

33 Console.WriteLine (a.ManName & "tikko saņēmu dakšiņu. Gaida")

34 Izmēģiniet

Thread.Sleep (100) Catch e kā izņēmuma konsole. WriteLine (e.StackTrace)

Beigt Izmēģiniet

35 Beigas Ja

36 Monitors. Iziet (es)

Beigt SyncLock

37 Beigu apakš

38 Beigu klase

39 Publiskās klases nazis

40 Private Shared mKnifeAvailable As Boolean = True

41 Privāts koplietots mOwner As String = "Neviens"

42 Privāts, tikai lasāms īpašums, kuram pieder Utensi1 () kā virkne

43 Iegūt

44 Atgrieziet mOwner

45 Beigt

46 Gala īpašums

47 Public Sub GrabKnifetByVal a kā programmētājs)

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

"mēģinot paķert nazi.")

49 Console.WriteLine (Me.OwnsUtensil & "ir nazis.")

50 Monitors. Ievadiet (es) "SyncLock (aKnife)"

51 Ja mKnifeAvailable Tad

52 mKnifeAvailable = Nepareizi

53 a. HasKnife = Patiess

54 m Īpašnieks = a.ManName

55 Console.WriteLine (a.ManName & "tikko dabūju nazi. Gaida")

56 Izmēģiniet

Vītne. Miega režīms (100)

Catch e kā izņēmums

Console.WriteLine (e.StackTrace)

Beigt Izmēģiniet

57 Beigas Ja

58 Monitors. Iziet (es)

59 Beigu apakš

60 Beigu klase

61 Publiskās klases programmētājs

62 Privāts mName As String

63 Privāts Kopīgs mFork Kā dakša

64 Privāts koplietošanas mKnife kā nazis

65 Privāts mHasKnife Kā Būla

66 Privāts mHasFork Kā Būla

67 Kopīgots jauns ()

68 mFork = jauna dakša ()

69 mKnazis = jauns nazis ()

70 Beigu apakš

71 Public Sub New (ByVal theName As String)

72 mVārds = vārds

73 Beigu apakš

74 Publisks tikai lasāms īpašums Mans vārds () kā virkne

75 Iegūt

76 Atgriezties mName

77 Beigt

78 Gala īpašums

79 Publiskais īpašums HasKnife () Kā Būla

80 Iegūt

81 Atgrieziet mHasKnife

82 Beigt

83 Iestatīt (ByVal vērtība kā Būla)

84 mHasKnife = Vērtība

85 Beigu komplekts

86 Gala īpašums

87 Publiskais īpašums HasFork () Kā Būla

88 Iegūt

89 Atgriezt mHasFork

90 Beigt

91 kopa (ByVal vērtība kā Būla)

92 mHasFork = Vērtība

93 Beigu komplekts

94 Gala īpašums

95 Publisks ēdiens ()

96 Dariet līdz man.HasKnife And Me.HasFork

97 Console.Writeline (Thread.CurrentThread.Name & "ir pavedienā.")

98 Ja Rnd ()< 0.5 Then

99 mFork.GrabFork (es)

100 Citādi

101 mKnife. GrabKnife (es)

102 Beigt Ja

103 Cilpa

104 MsgBox (Me.ManName & "var ēst!")

105 mKnazis = jauns nazis ()

106 mFork = jauna dakša ()

107 Beigu apakš

108 Beigu klase

Galvenā procedūra Main (4-16. Rindas) izveido divus Programmer klases eksemplārus un pēc tam sāk divus pavedienus, lai izpildītu Programmer klases kritisko Eat metodi (95.-108. Rindas), kas aprakstīta zemāk. Galvenā procedūra nosaka pavedienu nosaukumus un tos; droši vien viss notiekošais ir saprotams un bez komentāriem.

Dakšu klases kods izskatās interesantāks (17.-38. Rinda) (līdzīga Knife klase ir definēta 39.-60. Rindā). 18. un 19. rindā ir norādītas kopīgo lauku vērtības, pēc kurām jūs varat uzzināt, vai spraudnis pašlaik ir pieejams, un, ja nav, tad kurš to izmanto. Īpašums ReadOnly OwnUtensi1 (20.-24. Rindas) ir paredzēts vienkāršākajai informācijas pārsūtīšanai. Dakšu klases centrā ir GrabFork metode “satveriet dakšiņu”, kas definēta 25.-27.

  1. 26. un 27. rindā konsolē tiek vienkārši izdrukāta atkļūdošanas informācija. Metodes galvenajā kodā (28.-36. Rindā) piekļuve dakšai tiek sinhronizēta ar objektujosta Man. Tā kā mūsu programmā tiek izmantota tikai viena dakša, Me sinhronizācija nodrošina, ka divi pavedieni to nevar satvert vienlaicīgi. Komanda Slee "p (blokā, kas sākas 34. rindā) simulē kavēšanos starp dakšiņas / naža satveršanu un ēšanas sākšanu. Ņemiet vērā, ka komanda Miega režīms neatbloķē objektus un tikai paātrina strupceļu!
    Tomēr visinteresantākais ir Programmētāju klases kods (61.-108.rindas). 67. – 70. Rindā ir definēts vispārējs konstruktors, lai nodrošinātu, ka programmā ir tikai viena dakša un nazis. Īpašuma kods (74.-94.rindas) ir vienkāršs un neprasa komentārus. Vissvarīgākais notiek Eat metodē, kuru izpilda divi atsevišķi pavedieni. Process turpinās cilpā, līdz kāda straume kopā ar nazi uztver dakšiņu. 98-102 rindās objekts nejauši satver dakšiņu / nazi, izmantojot Rnd izsaukumu, kas izraisa strupceļu. Notiek sekojošais:
    Vītne, kas izpilda Tot ēdināšanas metodi, tiek izsaukta un nonāk cilpā. Viņš paķer nazi un nonāk gaidīšanas stāvoklī.
  2. Vītne, kas izpilda Boba ēšanas metodi, tiek izsaukta un nonāk cilpā. Tas nevar paķert nazi, bet tas satver dakšiņu un nonāk gaidstāves stāvoklī.
  3. Vītne, kas izpilda Tot ēdināšanas metodi, tiek izsaukta un nonāk cilpā. Viņš mēģina satvert dakšiņu, bet Bobs jau ir satvēris dakšiņu; pavediens nonāk gaidīšanas stāvoklī.
  4. Vītne, kas izpilda Boba ēšanas metodi, tiek izsaukta un nonāk cilpā. Viņš mēģina sagrābt nazi, bet nazi jau notver objekts Thots; pavediens nonāk gaidīšanas stāvoklī.

Tas viss turpinās uz nenoteiktu laiku - mēs saskaramies ar tipisku strupceļa situāciju (mēģiniet palaist programmu, un jūs redzēsit, ka šādā veidā neviens nevar ēst).
Varat arī pārbaudīt, vai pavedienu logā ir iestājies strupceļš. Palaidiet programmu un pārtrauciet to ar taustiņiem Ctrl + Break. Skatā iekļaujiet mainīgo Me un atveriet straumju logu. Rezultāts izskatās apmēram tāds, kā parādīts attēlā. 10.7. No attēla redzams, ka Boba pavediens ir paķēris nazi, bet tam nav dakšas. Ar peles labo pogu noklikšķiniet uz rindas Tot pavedienu logā un konteksta izvēlnē atlasiet komandu Pārslēgties uz pavedienu. Skata logā redzams, ka Thota strautam ir dakša, bet nav naža. Protams, tas nav simtprocentīgs pierādījums, taču šāda uzvedība vismaz liek aizdomāties, ka kaut kas nav kārtībā.
Ja opcija ar sinhronizāciju ar vienu objektu (kā programmā, palielinot temperatūru mājā) nav iespējama, lai novērstu savstarpēju bloķēšanu, varat numurēt sinhronizācijas objektus un vienmēr tos fiksēt nemainīgā secībā. Turpināsim ar pusdienu programmētāja analoģiju: ja pavediens vienmēr vispirms paņem nazi un pēc tam dakšiņu, ar bloķēšanu nebūs problēmu. Pirmā straume, kas satver nazi, varēs normāli ēst. Tulkojot programmu straumju valodā, tas nozīmē, ka 2. objekta uztveršana ir iespējama tikai tad, ja 1. objekts tiek pirmo reizi notverts.

Rīsi. 10.7. Vītņu loga strupceļu analīze

Tāpēc, ja mēs noņemam zvanu uz Rnd 98. rindā un aizstājam to ar fragmentu

mFork.GrabFork (es)

mKnife.GrabKnife (es)

strupceļš pazūd!

Sadarbojieties ar datiem, kad tie ir izveidoti

Vairāku pavedienu lietojumprogrammās bieži rodas situācija, kad pavedieni ne tikai darbojas ar koplietotiem datiem, bet arī gaida, līdz tie parādās (tas ir, 1. pavedienam ir jāizveido dati, pirms 2. pavediens to var izmantot). Tā kā dati tiek koplietoti, piekļuve tiem ir jāsinhronizē. Ir arī jāparedz līdzekļi, lai informētu gaidīšanas pavedienus par gatavu datu parādīšanos.

Šo situāciju parasti sauc piegādātāja / patērētāja problēma. Vītne mēģina piekļūt datiem, kas vēl nepastāv, tāpēc tai ir jāpārsūta kontrole uz citu pavedienu, kas izveido nepieciešamos datus. Problēma tiek atrisināta, izmantojot šādu kodu:

  • 1. pavediens (patērētājs) pamostas, ievada sinhronizētu metodi, meklē datus, tos neatrod un nonāk gaidīšanas stāvoklī. Provizoriskifiziski viņam jānoņem bloķēšana, lai netraucētu piegādes vītnes darbu.
  • 2. pavediens (nodrošinātājs) ievada sinhronizētu metodi, ko atbrīvo 1. pavediens, rada dati 1. straumei un kaut kā paziņo 1. straumei par datu klātbūtni. Pēc tam tas atbrīvo slēdzeni, lai pavediens 1 varētu apstrādāt jaunos datus.

Nemēģiniet atrisināt šo problēmu, pastāvīgi izsaucot 1. pavedienu un pārbaudot nosacījuma mainīgā stāvokli, kura vērtība ir> iestatīta ar 2. pavedienu. Šis lēmums nopietni ietekmēs jūsu programmas darbību, jo vairumā gadījumu 1. pavediens tiks izsaukts bez iemesls; un 2. pavediens gaidīs tik bieži, ka datu izveidei pietrūks laika.

Pakalpojumu sniedzēja / patērētāja attiecības ir ļoti izplatītas, tāpēc daudzpavedienu programmēšanas klases bibliotēkās šādām situācijām tiek radītas īpašas primitīvas. NET sistēmā šos primitīvos sauc Wait un Pulse-PulseAl 1, un tie ir daļa no Monitor klases. 10.8. Attēlā parādīta situācija, kuru mēs plānojam programmēt. Programma organizē trīs pavedienu rindas: gaidīšanas rindu, bloķēšanas rindu un izpildes rindu. Vītņu plānotājs nepiešķir CPU laiku pavedieniem, kas atrodas gaidīšanas rindā. Lai pavedienam tiktu piešķirts laiks, tam jāpāriet uz izpildes rindu. Tā rezultātā lietojumprogrammas darbs tiek organizēts daudz efektīvāk nekā ar parasto nosacītā mainīgā aptauju.

Pseidokodā datu patērētāja idioma tiek formulēta šādi:

"Ievadiet šāda veida sinhronizētu bloku

Lai gan nav datu

Dodieties uz gaidīšanas rindu

Cilpa

Ja ir dati, apstrādājiet tos.

Atstāt sinhronizēto bloku

Tūlīt pēc Wait komandas izpildes pavediens tiek apturēts, slēdzene tiek atbrīvota un pavediens nonāk gaidīšanas rindā. Atlaižot slēdzeni, izpildes rindā esošajai pavedienai ir atļauts darboties. Laika gaitā viens vai vairāki bloķēti pavedieni radīs datus, kas nepieciešami gaidīšanas rindā esošās pavediena darbībai. Tā kā datu validācija tiek veikta ciklā, pāreja uz datu izmantošanu (pēc cikla) ​​notiek tikai tad, ja ir gatavi apstrādei dati.

Pseidokodā datu sniedzēja idioma izskatās šādi:

"Sinhronizēta skata bloka ievadīšana

Kaut arī dati NAV nepieciešami

Dodieties uz gaidīšanas rindu

Citādi ražot datus

Kad dati ir gatavi, zvaniet Pulse-PulseAll.

lai pārvietotu vienu vai vairākus pavedienus no bloķēšanas rindas uz izpildes rindu. Atstājiet sinhronizēto bloku (un atgriezieties izpildes rindā)

Pieņemsim, ka mūsu programma simulē ģimeni ar vienu no vecākiem, kas pelna naudu, un bērnu, kurš šo naudu tērē. Kad nauda ir beigusiesizrādās, ka bērnam jāgaida, kad pienāks jauna summa. Šī modeļa programmatūras ieviešana izskatās šādi:

1 Opcija Stingri ieslēgta

2 Importēšanas sistēma

3 Moduļa modulis

4 Sub Main ()

5 Izgaismojiet ģimeni kā jaunu ģimeni ()

6 theFamily.StartltsLife ()

7 End Sub

8 Beigt fjodule

9

10 Sabiedriskās klases ģimene

11 Privāts mMoney kā vesels skaitlis

12 Privāta mWeek kā vesels skaitlis = 1

13 Public Sub StartltsLife ()

14 Dim aThreadStart As New ThreadStarUAddressOf Me.Produce)

15 Dim bThreadStart As New ThreadStarUAddressOf Me. Patērēt)

16 Dim aThread kā jauna pavediens (aThreadStart)

17 Dim bThread kā jauna pavediens (bThreadStart)

18 aThread.Name = "Ražot"

19 aVītne. Sākt ()

20 bThread.Name = "Patērēt"

21 bVītne. Sākt ()

22 Beigu apakš

23 Publiskais īpašums TheWeek () kā vesels skaitlis

24 Iegūt

25 Atgriešanās nedēļā

26 Beigt Iegūt

27 Iestatīt (ByVal vērtība kā vesels skaitlis)

28 nedēļas - vērtība

29 Beigu komplekts

30 Gala īpašums

31 Publiskais īpašums OurMoney () Kā vesels skaitlis

32 Iegūt

33 Atgrieziet mMoney

34 Beigt Iegūt

35 Iestatīt (ByVal vērtība kā vesels skaitlis)

36 mNauda = vērtība

37 Beigu komplekts

38 Gala īpašums

39 Publisks apakšražojums ()

40 pavediens. Miega režīms (500)

41 Dariet

42 Monitors. Ievadiet (mani)

43 Dariet kamēr es. Mūsu nauda> 0

44 Monitors. Pagaidiet (es)

45 Cilpa

46 Me. Mūsu nauda = 1000

47 Monitor.PulseAll (es)

48 Monitors. Iziet (es)

49 Cilpa

50 Beigu apakš

51 Publisks apakšpatēriņš ()

52 MsgBox ("Es esmu patērēšanas pavedienā")

53 Dariet

54 Monitors. Ievadiet (mani)

55 Dariet, kamēr es. Mūsu nauda = 0

56 Monitors. Pagaidiet (es)

57 Cilpa

58 Console.WriteLine ("Cienījamais vecāks, es tikko iztērēju visu tavu" & _

nauda nedēļā "un TheWeek)

59 Nedēļa + = 1

60 Ja nedēļa = 21 * 52, tad System.Environment.Exit (0)

61 Es. Mūsu nauda = 0

62 Monitors. PulseAll (es)

63 Monitors. Iziet (es)

64 Cilpa

65 Beigu apakš

66 Beigu klase

Metode StartltsLife (13.-22. Rindas) sagatavo, lai sāktu ražot un patērēt plūsmas. Vissvarīgākais notiek plūsmā Ražot (39.-50. Rindas) un Patērēt (51.-65. Rindas). Sub Produce procedūra pārbauda naudas pieejamību, un, ja ir nauda, ​​tā nonāk gaidīšanas rindā. Pretējā gadījumā vecāks ģenerē naudu (46. rinda) un paziņo gaidīšanas rindas objektiem par situācijas izmaiņām. Ņemiet vērā, ka zvans uz Pulse-Pulse All stājas spēkā tikai tad, kad ar komandu Monitor.Exit tiek atbrīvota bloķēšana. Turpretī procedūra Apakšpatēriņš pārbauda naudas pieejamību un, ja naudas nav, par to informē topošo vecāku. 60. līnija vienkārši pārtrauc programmu pēc 21 nosacīta gada; zvanot sistēmai. Environment.Exit (0) ir beigu komandas .NET analogs (tiek atbalstīta arī komanda End, bet atšķirībā no System. Environment. Exit, tā neatgriež operētājsistēmai izejas kodu).

Pavedieni, kas tiek ievietoti gaidīšanas rindā, ir jāatbrīvo citām programmas daļām. Šī iemesla dēļ mēs dodam priekšroku PulseAll pār Pulse. Tā kā iepriekš nav zināms, kurš pavediens tiks aktivizēts, kad tiks izsaukts Pulse 1, ja rindā ir salīdzinoši maz pavedienu, varat izsaukt PulseAll tikpat labi.

Daudzpavedieni grafikas programmās

Mūsu diskusija par daudzpavedienu GUI lietojumprogrammās sākas ar piemēru, kas izskaidro, kam domāta daudzpavedienu izmantošana GUI lietojumprogrammās. Izveidojiet veidlapu ar divām pogām Sākt (btnStart) un Atcelt (btnCancel), kā parādīts attēlā. 10.9. Noklikšķinot uz pogas Sākt, tiek izveidota klase, kurā ir nejauša 10 miljonu rakstzīmju virkne un metode, kā saskaitīt burta "E" gadījumus šajā garajā virknē. Ņemiet vērā StringBuilder klases izmantošanu, lai efektīvāk izveidotu garas virknes.

1. darbība

1. pavediens atzīmē, ka par to nav datu. Tas sauc Wait, atlaiž slēdzeni un pāriet uz gaidīšanas rindu.



2. solis

Kad slēdzene tiek atbrīvota, 2. pavediens vai 3. pavediens atstāj bloku rindu un nonāk sinhronizētā blokā, iegūstot bloķēšanu

3. solis

Pieņemsim, ka 3. pavediens ievada sinhronizētu bloku, izveido datus un izsauc Pulse-Pulse All.

Tūlīt pēc tam, kad tas iziet no bloka un atbrīvo slēdzeni, pavediens 1 tiek pārvietots uz izpildes rindu. Ja 3. pavediens izsauc Pluse, izpildes rindā nonāk tikai vienspavediens, kad tiek izsaukts Pluse All, visi pavedieni nonāk izpildes rindā.



Rīsi. 10.8. Piegādātāja / patērētāja problēma

Rīsi. 10.9. Daudzpavedieni vienkāršā GUI lietojumprogrammā

Imports System.Text

Publiskās klases izlases rakstzīmes

Privāti m_Data kā StringBuilder

Privāts mjength, m_count As Integer

Public Sub New (ByVal n As Integer)

m_ garums = n -1

m_Data = Jauns StringBuilder (m_length) MakeString ()

Beigu apakš

Privāta apakšstruktūra ()

Dim i As Integer

Dim myRnd kā jauns nejaušs ()

Attiecībā uz i = 0 līdz m_garumam

"Izveidojiet nejaušu skaitli no 65 līdz 90,

"pārvērst to par lielajiem burtiem

"un pievienojiet objektam StringBuilder

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

Nākamais

Beigu apakš

Public Sub StartCount ()

GetEes ()

Beigu apakš

Privāts apakšpakalpojums GetEes ()

Dim i As Integer

Attiecībā uz i = 0 līdz m_garumam

Ja m_Data.Chars (i) = CChar ("E") Tad

m_skaitlis + = 1

Beigt, ja nākamā

m_CountDone = Patiess

Beigu apakš

Publiski lasāms

Īpašums GetCount () kā vesels skaitlis

Ja nē (m_CountDone) Tad

Atgriezt m_count

Beigas Ja

Beigt Iegūt beigu īpašumu

Publiski lasāms

Īpašums tiek veikts () kā Būla

Atgriezties

m_CountDone

Beigt Iegūt

Beigu īpašums

Beigt klasi

Ar divām veidlapas pogām ir saistīts ļoti vienkāršs kods. Procedūra btn-Start_Click izveido iepriekšminēto RandomCharacters klasi, kas ietver virkni ar 10 miljoniem rakstzīmju:

Privāts apakšbtnStart_Click (ByVal sūtītājs kā System.Object.

ByVal e As System.EventArgs) Rokturi btnSTart.Click

Dim RC kā jaunas izlases rakstzīmes (10000000)

RC.StartCount ()

MsgBox ("Es skaits ir" un RC.GetCount)

Beigu apakš

Poga Atcelt atver ziņojumu lodziņu:

Privāts apakšbtnCancel_Click (ByVal sūtītājs kā System.Object._

ByVal e As System.EventArgs) Rokturi btnCancel.Click

MsgBox ("Skaits pārtraukts!")

Beigu apakš

Palaižot programmu un nospiežot pogu Sākt, izrādās, ka poga Atcelt neatbild uz lietotāja ievadīto informāciju, jo nepārtrauktā cilpa neļauj pogai apstrādāt saņemto notikumu. Mūsdienu programmās tas ir nepieņemami!

Ir divi iespējamie risinājumi. Pirmā opcija, kas labi pazīstama no iepriekšējām VB versijām, atsakās no vairāku pavedienu izmantošanas: DoEvents zvans ir iekļauts ciklā. Tīklā NET šī komanda izskatās šādi:

Application.DoEvents ()

Mūsu piemērā tas noteikti nav vēlams - kurš vēlas palēnināt programmu ar desmit miljoniem DoEvents zvanu! Ja tā vietā cilpu piešķirat atsevišķam pavedienam, operētājsistēma pārslēgsies starp pavedieniem un poga Atcelt paliks funkcionāla. Īstenošana ar atsevišķu pavedienu ir parādīta zemāk. Lai skaidri parādītu, ka poga Atcelt darbojas, noklikšķinot uz tās, mēs vienkārši pārtraucam programmu.

Nākamais solis: rādīt pogu Rādīt

Pieņemsim, ka jūs nolēmāt parādīt savu radošo iztēli un piešķirt veidlapai izskatu, kas parādīts attēlā. 10.9. Lūdzu, ņemiet vērā: poga Rādīt skaitu vēl nav pieejama.

Rīsi. 10.10. Bloķēta pogas forma

Paredzams, ka atsevišķa pavediens veiks skaitīšanu un atbloķēs nepieejamo pogu. To, protams, var izdarīt; turklāt šāds uzdevums rodas diezgan bieži. Diemžēl jūs nevarēsit rīkoties visredzamākajā veidā - saistīt sekundāro pavedienu ar GUI pavedienu, saglabājot saiti uz konstruktora pogu ShowCount vai pat izmantojot standarta delegātu. Citiem vārdiem sakot, nekad neizmantojiet tālāk norādīto opciju (pamata kļūdains līnijas ir treknrakstā).

Publiskās klases izlases rakstzīmes

Privāts m_0ata kā StringBuilder

Privāts m_CountDone Kā Būla

Privāts mjength. m_count kā vesels skaitlis

Privāta m_Button Kā Windows.Forms.Button

Public Sub New (ByVa1 n Kā vesels skaitlis, _

ByVal b Kā Windows.Forms.Button)

m_garums = n - 1

m_Data = Jauns StringBuilder (mJength)

m_Button = b MakeString ()

Beigu apakš

Privāta apakšstruktūra ()

Dim I As Integer

Dim myRnd kā jauns nejaušs ()

I = 0 līdz m_garumam

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

Nākamais

Beigu apakš

Public Sub StartCount ()

GetEes ()

Beigu apakš

Privāts apakšpakalpojums GetEes ()

Dim I As Integer

I = 0 līdz mjength

Ja m_Data.Chars (I) = CChar ("E") Tad

m_skaitlis + = 1

Beigt, ja nākamā

m_CountDone = Patiess

m_Button.Enabled = Patiess

Beigu apakš

Publiski lasāms

Īpašums GetCount () kā vesels skaitlis

gūt

Ja nē (m_CountDone) Tad

Mest jaunu izņēmumu ("Skaits vēl nav izdarīts") Cits

Atgriezt m_count

Beigas Ja

Beigt Iegūt

Beigu īpašums

Publisks tikai lasāms īpašums tiek veikts () kā Būla

gūt

Atgriezt m_CountDone

Beigt Iegūt

Beigu īpašums

Beigt klasi

Iespējams, ka šis kods dažos gadījumos darbosies. Tomēr:

  • Nevar organizēt sekundārā pavediena mijiedarbību ar pavedienu, kas rada GUI acīmredzams nozīmē.
  • Nekad nemainiet grafisko programmu elementus no citām programmu plūsmām. Visām izmaiņām vajadzētu notikt tikai tajā pavedienā, kas izveidoja GUI.

Ja jūs pārkāpjat šos noteikumus, mēs mēs garantējam ka jūsu daudzpavedienu grafikas programmās radīsies smalkas, smalkas kļūdas.

Tāpat nebūs iespējams organizēt objektu mijiedarbību, izmantojot notikumus. 06 notikumu darbinieks darbojas tajā pašā pavedienā, ko sauca RaiseEvent, tāpēc pasākumi jums nepalīdzēs.

Tomēr veselais saprāts nosaka, ka grafiskajām lietojumprogrammām ir jābūt līdzekļiem citu pavedienu elementu modificēšanai. NET Framework ir pavedienam drošs veids, kā izsaukt GUI lietojumprogrammu metodes no cita pavediena. Īpašs Method Invoker delegāta veids no sistēmas. Šim nolūkam tiek izmantota Windows nosaukumvieta. Veidlapas. Šis fragments parāda jaunu GetEes metodes versiju (mainītas rindas treknrakstā):

Privāts apakšpakalpojums GetEes ()

Dim I As Integer

I = 0 līdz m_garumam

Ja m_Data.Chars (I) = CChar ("E") Tad

m_skaitlis + = 1

Beigt, ja nākamā

m_CountDone = Patiesi Izmēģiniet

Dim mylnvoker kā jauns Methodlnvoker (AddressOf UpDateButton)

myInvoker.Invoke () Catch e As ThreadlnterruptException

"Neveiksme

Beigt Izmēģiniet

Beigu apakš

Public Sub UpDateButton ()

m_Button.Enabled = Patiess

Beigu apakš

Izsaukumi starp pavedieniem uz pogu tiek veikti nevis tieši, bet izmantojot Method Invoker. .NET Framework garantē, ka šī opcija ir droša pavedieniem.

Kāpēc ir tik daudz problēmu ar daudzpavedienu programmēšanu?

Tagad, kad jums ir zināma izpratne par daudzpavedienu un ar to saistītajām iespējamām problēmām, mēs nolēmām, ka būtu lietderīgi atbildēt uz jautājumu, kas minēts šīs apakšnodaļas virsrakstā šīs nodaļas beigās.

Viens no iemesliem ir tas, ka daudzpavedieni ir nelineārs process, un mēs esam pieraduši pie lineārās programmēšanas modeļa. Sākumā ir grūti pierast pie domas, ka programmas izpildi var pārtraukt nejauši, un vadība tiks pārnesta uz citu kodu.

Tomēr ir vēl viens, daudz būtiskāks iemesls: mūsdienās programmētāji pārāk reti programmē montētāju vai vismaz aplūko kompilatora izjaukto izvadi. Pretējā gadījumā viņiem būtu daudz vieglāk pierast pie domas, ka desmitiem montāžas instrukciju var atbilst vienai augsta līmeņa valodas komandai (piemēram, VB .NET). Pavedienu var pārtraukt pēc jebkuras no šīm instrukcijām un līdz ar to arī augsta līmeņa komandas vidū.

Bet tas vēl nav viss: mūsdienu kompilatori optimizē programmas veiktspēju, un datoru aparatūra var traucēt atmiņas pārvaldību. Tā rezultātā kompilators vai aparatūra bez jūsu ziņas var mainīt programmas avota kodā norādīto komandu secību [ Daudzi kompilatori optimizē masīvu ciklisku kopēšanu, piemēram, i = 0 līdz n: b (i) = a (i): ncxt. Sastādītājs (vai pat specializēts atmiņas pārvaldnieks) var vienkārši izveidot masīvu un pēc tam aizpildīt to ar vienu kopēšanas darbību, nevis daudzkārt kopēt atsevišķus elementus!].

Cerams, ka šie skaidrojumi palīdzēs jums labāk izprast, kāpēc daudzpavedienu programmēšana rada tik daudz problēmu - vai vismaz mazāk izbrīna par jūsu daudzšķiedru programmu dīvaino uzvedību!

Vienkāršas vairāku pavedienu lietojumprogrammas veidošanas piemērs.

Dzimis daudzu jautājumu dēļ par daudzpavedienu lietojumprogrammu veidošanu Delfos.

Šī piemēra mērķis ir parādīt, kā pareizi izveidot vairāku pavedienu lietojumprogrammu, pārņemot ilgtermiņa darbu atsevišķā pavedienā. Un kā šādā lietojumprogrammā nodrošināt galvenā pavediena mijiedarbību ar strādnieku, lai pārsūtītu datus no formas (vizuālie komponenti) uz straumi un otrādi.

Piemērs nepretendē uz to, ka tas ir pilnīgs, tas parāda tikai vienkāršākos pavedienu mijiedarbības veidus. Ļaujot lietotājam "ātri padarīt aklu" (kurš zinātu, cik ļoti es to ienīstu) pareizi strādājošu daudzpavedienu lietojumprogrammu.
Tajā viss ir detalizēti komentēts (manuprāt), bet, ja jums ir kādi jautājumi, jautājiet.
Bet es vēlreiz brīdinu: Straumes nav vieglas... Ja jums nav ne jausmas, kā tas viss darbojas, tad pastāv milzīgas briesmas, ka bieži vien viss jums darbosies labi, un dažreiz programma uzvedīsies vairāk nekā dīvaini. Nepareizi uzrakstītas daudzpavedienu programmas darbība ir ļoti atkarīga no daudziem faktoriem, kurus dažreiz nevar atveidot atkļūdošanas laikā.

Tātad piemērs. Ērtības labad esmu ievietojis gan kodu, gan pievienojis arhīvu ar moduļa un veidlapas kodu

vienība ExThreadForm;

izmanto
Windows, ziņojumi, SysUtils, varianti, klases, grafika, vadīklas, veidlapas,
Dialogi, StdCtrls;

// konstantes, ko izmanto, pārsūtot datus no straumes uz veidlapu, izmantojot
// sūtīt loga ziņas
konst
WM_USER_SendMessageMetod = WM_USER + 10;
WM_USER_PostMessageMetod = WM_USER + 11;

tipa
// pavediena klases apraksts, tThread pēcnācējs
tMyThread = klase (tThread)
Privāts
SyncDataN: vesels skaitlis;
SyncDataS: String;
procedūra SyncMetod1;
aizsargāti
procedūra Izpildīt; ignorēt;
publiski
Param1: virkne;
Param2: vesels skaitlis;
Param3: Būla;
Apturēts: Būla;
LastRandom: vesels skaitlis;
IterationNo: vesels skaitlis;
Rezultātu saraksts: tStringList;

Konstruktoru izveide (aParam1: String);
iznīcinātājs Iznīcini; ignorēt;
beigas;

// veidlapas klases apraksts, izmantojot straumi
TForm1 = klase (TForm)
Iezīme1: TLabel;
1. piezīme: TMemo;
btnStart: TButton;
btnStop: TButton;
Edit1: TEdit;
Edit2: TEdit;
CheckBox1: TCheckBox;
2. iezīme: TLabel;
3. iezīme: TLabel;
4. iezīme: TLabel;
procedūra btnStartClick (Sūtītājs: TObject);
procedūra btnStopClick (Sūtītājs: TObject);
Privāts
(Privātas deklarācijas)
MyThread: tMyThread;
procedūra EventMyThreadOnTerminate (Sūtītājs: tObject);
procedūra EventOnSendMessageMetod (var Msg: TMessage); ziņojums WM_USER_SendMessageMetod;
procedūra EventOnPostMessageMetod (var Msg: TMessage); ziņojums WM_USER_PostMessageMetod;

Publisks
(Publiskās deklarācijas)
beigas;

var
1. veidlapa: TForm1;

{
Apturēts - parāda datu pārsūtīšanu no veidlapas uz straumi.
Papildu sinhronizācija nav nepieciešama, jo tā ir vienkārša
viens vārds, un to raksta tikai viens pavediens.
}

procedūra TForm1.btnStartClick (Sūtītājs: TObject);
sākt
Randomize (); // nejaušības nodrošināšana secībā ar Random () - tam nav nekāda sakara ar plūsmu

// Izveidojiet straumes objekta gadījumu, nododot tam ievades parametru
{
UZMANĪBU!
Plūsmas konstruktors ir uzrakstīts tā, lai plūsma tiktu izveidota
apturēts, jo tas ļauj:
1. Kontrolējiet tā palaišanas brīdi. Tas gandrīz vienmēr ir ērtāk, jo
ļauj iestatīt straumi pat pirms sākuma, nodot tās ievadi
parametri utt.
2. Jo saite uz izveidoto objektu tiks saglabāta veidlapas laukā, pēc tam
pēc pavediena pašiznīcināšanās (skat. zemāk), kas, kad pavediens darbojas
var rasties jebkurā laikā, šī saite kļūs nederīga.
}
MyThread: = tMyThread.Create (Form1.Edit1.Text);

// Tomēr, tā kā pavediens tika izveidots apturēts, tad uz jebkādām kļūdām
// tās inicializācijas laikā (pirms uzsākšanas) mums pašiem tas jāiznīcina
// tam, ko mēs izmantojam, izmēģiniet / izņemot bloku
pamēģini

// pavedienu pārtraukšanas apstrādātāja piešķiršana, kurā mēs saņemsim
// straumes darba rezultātus, un "pārrakstīt" saiti uz to
MyThread.OnTerminate: = EventMyThreadOnTerminate;

// Tā kā rezultāti tiks apkopoti OnTerminate, t.i. pirms pašiznīcināšanās
// straume, tad mēs noņemsim rūpes par tās iznīcināšanu
MyThread.FreeOnTerminate: = True;

// Piemērs ievades parametru nodošanai caur straumes objekta laukiem punktā
// momentāni, kad vēl nedarbojas.
// Personīgi es labprātāk to daru, izmantojot ignorējamos parametrus
// konstruktors (tMyThread.Create)
MyThread.Param2: = StrToInt (Form1.Edit2.Text);

MyThread.Stopped: = False; // arī sava veida parametrs, bet mainās
// pavedienu darbības laiks
izņemot
// tā kā pavediens vēl nav sākts un nevarēs pašiznīcināties, mēs to iznīcināsim "manuāli"
FreeAndNil (MyThread);
// un tad ļaujiet izņēmumam rīkoties kā parasti
paaugstināt;
beigas;

// Tā kā pavedienu objekts ir veiksmīgi izveidots un konfigurēts, ir pienācis laiks to sākt
MyThread.Resume;

ShowMessage ("Straume sākta");
beigas;

procedūra TForm1.btnStopClick (Sūtītājs: TObject);
sākt
// Ja pavedienu gadījums joprojām pastāv, lūdziet to pārtraukt
// Un, tieši "pajautā". Principā mēs varam arī "piespiest", bet būs
// ārkārtas ārkārtas iespēja, kas prasa visu to skaidri saprast
// straumēšanas virtuve. Tāpēc šeit tas netiek ņemts vērā.
ja piešķirts (MyThread), tad
MyThread.Stopped: = True
citādi
ShowMessage ("pavediens nedarbojas!");
beigas;

procedūra TForm1.EventOnSendMessageMetod (var Msg: TMessage);
sākt
// metode sinhronā ziņojuma apstrādei
// programmā WParam tMyThread objekta adrese, LParam pavediena LastRandom pašreizējā vērtība
ar tMyThread (Msg.WParam) sākas
Form1.Label3.Caption: = Format ("% d% d% d",);
beigas;
beigas;

procedūra TForm1.EventOnPostMessageMetod (var Msg: TMessage);
sākt
// metode asinhronā ziņojuma apstrādei
// programmā WParam pašreizējā IterationNo vērtība, LParam - LastRandom straumes pašreizējā vērtība
Form1.Label4.Caption: = Format ("% d% d",);
beigas;

procedūra TForm1.EventMyThreadOnTerminate (Sūtītājs: tObject);
sākt
// SVARĪGS!
// OnTerminate notikuma apstrādes metode vienmēr tiek izsaukta saistībā ar galveno
// pavediens - to garantē tThread ieviešana. Tāpēc tajā jūs varat brīvi
// izmantot jebkuru objektu īpašības un metodes

// Katram gadījumam pārliecinieties, vai objekta instance joprojām pastāv
ja nav piešķirts (MyThread), tad izejiet; // ja tā nav, tad nav ko darīt

// iegūt pavediena objekta instances pavediena darba rezultātus
Form1.Memo1.Lines.Add (Format ("Straume beidzās ar rezultātu% d",));
Form1.Memo1.Lines.AddStrings ((sūtītājs kā tMyThread) .ResultList);

// Iznīciniet atsauci uz straumes objekta instanci.
// Tā kā mūsu pavediens ir pašiznīcinošs (FreeOnTerminate: = True)
// pēc tam, kad OnTerminate apstrādātājs būs pabeidzis, straumes objekta instance būs
// tiek iznīcināts (bezmaksas), un visas atsauces uz to kļūs nederīgas.
// Lai nejauši neiekļūtu šādā saitē, pārrakstiet MyThread
// Vēlreiz atzīmēšu - mēs objektu neiznīcināsim, bet tikai pārrakstīsim saiti. Objekts
// iznīcināt sevi!
MyThread: = Nē;
beigas;

konstruktors tMyThread.Create (aParam1: String);
sākt
// Izveidojiet SUSPENDED straumes instanci (skatiet komentāru, kad to izveidojat)
iedzimta Izveidot (True);

// Izveidojiet iekšējos objektus (ja nepieciešams)
Rezultātu saraksts: = tStringList.Create;

// Iegūstiet sākotnējos datus.

// Kopējiet parametra ievadītos datus
Param1: = aParam1;

// Piemērs ieejas datu saņemšanai no VCL komponentiem straumes objekta konstruktorā
// Tas šajā gadījumā ir pieņemami, jo konstruktoru sauc kontekstā
// galvenais pavediens. Tāpēc VCL komponentiem var piekļūt šeit.
// Bet man tas nepatīk, jo, manuprāt, ir slikti, ja pavediens kaut ko zina
// par kādu formu tur. Bet ko jūs nevarat darīt demonstrācijai.
Param3: = Form1.CheckBox1.Checked;
beigas;

destructor tMyThread.Destroy;
sākt
// iekšējo objektu iznīcināšana
FreeAndNil (rezultātu saraksts);
// iznīcināt pamatni tThread
iedzimta;
beigas;

procedūra tMyThread.Execute;
var
t: kardināls;
s: Stīga;
sākt
IterationNo: = 0; // rezultātu skaitītājs (cikla numurs)

// Manā piemērā pavediena pamatteksts ir cilpa, kas beidzas
// vai ar ārēju "pieprasījumu" izbeigt caur mainīgo parametru Stopped,
// vai nu tikai veicot 5 cilpas
// Man ir patīkamāk to rakstīt caur "mūžīgo" cilpu.

Lai gan patiesība sākas

Inc (atkārtojuma Nr.); // nākamā cikla numurs

LastRandom: = Nejaušs (1000); // atslēgas numurs - lai demonstrētu parametru pārnešanu no straumes uz veidlapu

T: = nejaušs (5) +1; // laiks, par kuru mēs aizmigsim, ja nebūsim pabeigti

// mēms darbs (atkarībā no ievades parametra)
ja ne Param3, tad
Inc (Param2)
citādi
Dec (Param2);

// Izveidojiet starpposma rezultātu
s: = Formāts ("% s% 5d% s% d% d",
);

// Pievienot rezultātu sarakstam starpposma rezultātu
ResultList.Add (s);

//// Piemēri starpposma rezultāta nodošanai veidlapai

//// Sinhronizētas metodes iziešana - klasiskais veids
//// Trūkumi:
//// - sinhronizētā metode parasti ir straumes klases metode (lai piekļūtu
//// uz straumes objekta laukiem), bet, lai piekļūtu veidlapas laukiem, tam ir jābūt
//// "zināt" par to un tā laukiem (objektiem), kas parasti nav īpaši labi
//// programmas organizācijas viedoklis.
//// - pašreizējā pavediena darbība tiks apturēta, līdz izpilde būs pabeigta
//// sinhronizēta metode.

//// Priekšrocības:
//// - standarta un daudzpusīgs
//// - sinhronizētā metodē varat izmantot
//// visi straumes objekta lauki.
// vispirms, ja nepieciešams, jums ir jāsaglabā pārsūtītie dati
// objekta objekta īpašie lauki.
SyncDataN: = IterationNo;
SyncDataS: = "Sinhronizēt" + s;
// un pēc tam nodrošiniet sinhronizētas metodes zvanu
Sinhronizēt (SyncMetod1);

//// Sūtīšana, izmantojot sinhronu ziņojumu sūtīšanu (SendMessage)
//// šajā gadījumā datus var nodot gan caur ziņojuma parametriem (LastRandom),
//// un caur objekta laukiem, ziņojuma parametrā nododot instances adresi
//// straumes objekts - vesels skaitlis (Self).
//// Trūkumi:
//// - pavedienam ir jāzina veidlapas loga rokturis
//// - tāpat kā sinhronizēt, pašreizējā pavediena darbība tiks apturēta līdz
//// ziņojumu apstrādes pabeigšana ar galveno pavedienu
//// - prasa ievērojamu CPU laiku katram zvanam
//// (lai mainītu pavedienus), tāpēc ļoti bieža zvanīšana ir nevēlama
//// Priekšrocības:
//// - tāpat kā sinhronizēt, apstrādājot ziņojumu, varat izmantot
//// visi straumes objekta lauki (ja, protams, tika nodota tā adrese)


//// sākt pavedienu.
SendMessage (veidlapa 1.Handle, WM_USER_SendMessageMetod, Integer (Self), LastRandom);

//// Pārsūtīšana, nosūtot asinhronu ziņojumu (PostMessage)
//// Tā kā šajā gadījumā līdz brīdim, kad ziņojums tiek saņemts galvenajā pavedienā,
//// sūtīšanas straume, iespējams, jau ir pabeigta, nododot instances adresi
//// straumes objekts nav derīgs!
//// Trūkumi:
//// - pavedienam jāzina veidlapas loga rokturis;
//// - asinhronijas dēļ datu pārsūtīšana ir iespējama tikai caur parametriem
//// ziņojumus, kas ievērojami sarežģī datu pārsūtīšanu, kuriem ir izmērs
//// vairāk nekā divi mašīnvārdi. To ir ērti izmantot, lai nokārtotu veselu skaitli utt.
//// Priekšrocības:
//// - atšķirībā no iepriekšējām metodēm pašreizējais pavediens NAV
//// ir apturēta un nekavējoties atsāks izpildi
//// - atšķirībā no sinhronizēta zvana ziņu apstrādātājs
//// ir veidlapas metode, kurai jābūt zināšanām par straumes objektu,
//// vai vispār neko nezināt par straumi, ja dati tiek pārsūtīti tikai
////, izmantojot ziņojuma parametrus. Tas ir, pavediens var neko nezināt par formu.
//// parasti - tikai viņas rokturis, ko iepriekš var nodot kā parametru
//// sākt pavedienu.
PostMessage (veidlapa 1.Handle, WM_USER_PostMessageMetod, IterationNo, LastRandom);

//// Pārbaudiet iespējamo pabeigšanu

// Pabeigt pēc parametra
ja apstājās, tad pārtraukums;

// Reizēm pārbaudiet pabeigtību
ja IterationNo> = 10, tad Break;

Miega režīms (t * 1000); // aizmigt uz t sekundēm
beigas;
beigas;

procedūra tMyThread.SyncMetod1;
sākt
// šī metode tiek izsaukta, izmantojot sinhronizācijas metodi.
// Tas ir, neskatoties uz to, ka tā ir tMyThread pavediena metode,
// tas darbojas lietojumprogrammas galvenā pavediena kontekstā.
// Tāpēc viņš var darīt jebko, labi, vai gandrīz visu :)
// Bet atcerieties, ka šeit nav vērts "pļāpāt" ilgu laiku

// Nosūtītos parametrus mēs varam iegūt no īpašajiem laukiem, kur tie mums ir
// saglabāts pirms zvanīšanas.
Form1.Label1.Caption: = SyncDataS;

// vai nu no citiem straumes objekta laukiem, piemēram, atspoguļojot tā pašreizējo stāvokli
Form1.Label2.Caption: = Format ("% d% d",);
beigas;

Kopumā pirms piemēra man bija sekojošs pamatojums par tēmu ...

Pirmkārt:
SVARĪGĀKAIS vairāku pavedienu programmēšanas noteikums Delfos ir šāds:
Saistībā ar pavedienu, kas nav galvenais, nav iespējams piekļūt veidlapu īpašībām un metodēm, kā arī visiem komponentiem, kas "izaug" no tWinControl.

Tas nozīmē (nedaudz vienkāršoti), ka ne izpildes metodē, kas mantota no TThread, ne citās metodēs / procedūrās / funkcijās, kas izsauktas no izpildes, tas ir aizliegts tieši piekļūt jebkurām vizuālo komponentu īpašībām un metodēm.

Kā to izdarīt pareizi.
Nav vienotu recepšu. Precīzāk, ir tik daudz un dažādu iespēju, ka atkarībā no konkrētā gadījuma jums ir jāizvēlas. Tāpēc viņi atsaucas uz rakstu. Izlasījis un sapratis, programmētājs varēs saprast un kā vislabāk to darīt konkrētā gadījumā.

Īsumā uz pirkstiem:

Visbiežāk daudzpavedienu lietojumprogramma kļūst vai nu tad, kad ir nepieciešams veikt kādu ilgtermiņa darbu, vai arī tad, ja ir iespējams vienlaikus veikt vairākas lietas, kas procesoru stipri neapgrūtina.

Pirmajā gadījumā darba īstenošana galvenajā pavedienā noved pie lietotāja saskarnes "palēnināšanās" - kamēr tiek veikts darbs, ziņojuma cilpa netiek izpildīta. Tā rezultātā programma nereaģē uz lietotāju darbībām, un veidlapa netiek uzzīmēta, piemēram, pēc lietotāja pārvietošanas.

Otrajā gadījumā, kad darbs saistīts ar aktīvu apmaiņu ar ārpasauli, tad piespiedu "dīkstāves" laikā. Gaidot datu saņemšanu / nosūtīšanu, paralēli varat darīt kaut ko citu, piemēram, vēlreiz nosūtīt / saņemt datus.

Ir arī citi gadījumi, bet retāk. Tomēr tas nav svarīgi. Tagad par to nav runa.

Tagad, kā tas viss ir uzrakstīts. Protams, tiek apsvērts viens no visbiežāk sastopamajiem, nedaudz vispārinātiem gadījumiem. Tātad.

Darbam, kas veikts atsevišķā pavedienā, parasti ir četras vienības (es nezinu, kā to precīzāk nosaukt):
1. Sākotnējie dati
2. Patiesībā pats darbs (tas var būt atkarīgs no sākotnējiem datiem)
3. Starpposma dati (piemēram, informācija par darba izpildes pašreizējo stāvokli)
4. Izejas dati (rezultāts)

Visbiežāk vizuālos komponentus izmanto, lai nolasītu un parādītu lielāko daļu datu. Bet, kā minēts iepriekš, jūs nevarat tieši piekļūt vizuālajiem komponentiem no straumes. Kā būt?
Delphi izstrādātāji iesaka izmantot TThread klases sinhronizācijas metodi. Šeit es neaprakstīšu, kā to izmantot - tam ir iepriekš minētais raksts. Ļaujiet man teikt, ka tās piemērošana, pat pareiza, ne vienmēr ir pamatota. Pastāv divas problēmas:

Pirmkārt, metodes sinhronizācija, kas tiek izsaukta, izmantojot sinhronizāciju, vienmēr tiek izpildīta galvenā pavediena kontekstā, un tāpēc, kamēr tā tiek izpildīta, loga ziņojumu cilpa atkal netiek izpildīta. Tāpēc tas ir jāizpilda ātri, pretējā gadījumā mēs iegūsim visas tās pašas problēmas, kas rodas, īstenojot vienu pavedienu. Ideālā gadījumā metodi, ko sauc, izmantojot sinhronizāciju, parasti vajadzētu izmantot tikai, lai piekļūtu vizuālo objektu īpašībām un metodēm.

Otrkārt, metodes izpilde, izmantojot sinhronizāciju, ir "dārgs" prieks, jo ir nepieciešami divi slēdži starp pavedieniem.

Turklāt abas problēmas ir savstarpēji saistītas un rada pretrunu: no vienas puses, lai atrisinātu pirmo, jums ir jāsasmalcina metodes, kas izsauktas, izmantojot sinhronizāciju, un, no otras puses, tās ir jāzvana biežāk, zaudējot dārgo procesora resursi.

Tāpēc, kā vienmēr, ir jāpieiet saprātīgi un dažādos gadījumos jāizmanto dažādi plūsmas mijiedarbības veidi ar ārpasauli:

Sākotnējie dati
Visi dati, kas tiek pārsūtīti uz straumi un nemainās tās darbības laikā, ir jāpārnes pat pirms tās sākuma, t.i. veidojot straumi. Lai tos izmantotu pavediena pamattekstā, jums ir jāizveido to lokāla kopija (parasti TThread pēcteča laukos).
Ja ir sākotnējie dati, kas var mainīties, kamēr pavediens darbojas, tad šādiem datiem jāpiekļūst vai nu, izmantojot sinhronizētas metodes (metodes, kas izsauktas, izmantojot sinhronizāciju), vai caur pavedienu objekta laukiem (TThread pēcnācējs). Pēdējais prasa zināmu piesardzību.

Starpposma un izvades dati
Šeit atkal ir vairāki veidi (pēc manas izvēles):
- Ziņojumu asinhronās nosūtīšanas metode uz lietojumprogrammas galveno logu.
To parasti izmanto, lai nosūtītu ziņojumus par procesa gaitu uz galveno lietojumprogrammas logu, pārsūtot nelielu datu apjomu (piemēram, pabeigtības procentu)
- Ziņu sinhronas nosūtīšanas metode uz lietojumprogrammas galveno logu.
To parasti izmanto tādiem pašiem mērķiem kā asinhrono sūtīšanu, taču tas ļauj pārsūtīt lielāku datu apjomu, neveidojot atsevišķu kopiju.
- Sinhronizētas metodes, ja iespējams, apvienojot pēc iespējas vairāk datu pārsūtīšanu vienā metodē.
Var izmantot arī datu izgūšanai no veidlapas.
- Izmantojot straumes objekta laukus, nodrošinot savstarpēji izslēdzošu piekļuvi.
Sīkāku informāciju var atrast rakstā.

Eh. Uz īsu brīdi tas neizdevās