Pavedienu sinhronizācijas rīki operētājsistēmā Windows OS (kritiskās sadaļas, mutexi, semafori, notikumi). Sinhronizācijas objekti sistēmā Windows Procesu sinhronizēšana, izmantojot notikumus

Lekcija Nr. 9. Procesu un pavedienu sinhronizēšana

1. Sinhronizācijas mērķi un līdzekļi.

2. Sinhronizācijas mehānismi.

1. Sinhronizācijas mērķi un līdzekļi

Ir diezgan plaša operētājsistēmas rīku klase, kas nodrošina savstarpēju procesu un pavedienu sinhronizāciju. Vajadzība pēc pavedienu sinhronizācijas rodas tikai daudzprogrammu operētājsistēmā un ir saistīta ar kopīgu datorsistēmas aparatūras un informācijas resursu izmantošanu. Sinhronizācija ir nepieciešama, lai izvairītos no sacensībām un strupceļiem, apmainoties ar datiem starp pavedieniem, koplietojot datus un piekļūstot procesoram un I/O ierīcēm.

Daudzās operētājsistēmās šos rīkus sauc par starpprocesu komunikācijas rīkiem (IPC), kas atspoguļo jēdziena “process” vēsturisko prioritāti attiecībā pret jēdzienu “pavediens”. Parasti IPC rīki ietver ne tikai starpprocesu sinhronizācijas rīkus, bet arī starpprocesu datu apmaiņas rīkus.

Vītnes izpilde daudzprogrammēšanas vidē vienmēr ir asinhrona. Ir ļoti grūti ar pilnīgu pārliecību pateikt, kurā izpildes stadijā būs process noteiktā laika posmā. Pat vienas programmas režīmā ne vienmēr ir iespējams precīzi novērtēt laiku, kas nepieciešams uzdevuma izpildei. Šis laiks daudzos gadījumos būtiski ir atkarīgs no avota datu vērtības, kas ietekmē ciklu skaitu, programmas sazarošanas virzienu, I/O operāciju izpildes laiku utt. Tā kā avota dati dažādos laikos, kad uzdevums ir uzsāktais var būt atšķirīgs, tāpēc atsevišķu posmu izpildes laiks un uzdevums kopumā ir ļoti nenoteikta vērtība.


Vēl nenoteiktāks ir programmas izpildes laiks daudzprogrammēšanas sistēmā. Brīži, kad pavedieni tiek pārtraukti, laiks, ko tie pavada koplietojamo resursu rindās, secība, kādā pavedieni tiek atlasīti izpildei - visi šie notikumi ir daudzu apstākļu saplūšanas rezultāts un var tikt interpretēti kā nejauši. Labākajā gadījumā var novērtēt skaitļošanas procesa varbūtības raksturlielumus, piemēram, tā pabeigšanas varbūtību noteiktā laika periodā.

Tādējādi pavedieni vispārējā gadījumā (kad programmētājs nav veicis īpašus pasākumus to sinhronizēšanai) plūst neatkarīgi, asinhroni viens ar otru. Tas attiecas gan uz pavedieniem vienā un tajā pašā procesā, kas izpilda kopīgu programmas kodu, gan uz pavedieniem dažādos procesos, katrs izpilda savu programmu.

Jebkura mijiedarbība starp procesiem vai pavedieniem ir saistīta ar tiem sinhronizācija, kas sastāv no to ātruma koordinēšanas, apturot plūsmu līdz noteikta notikuma iestāšanās brīdim un pēc tam aktivizējot to, kad notiek šis notikums. Sinhronizācija ir jebkuras pavedienu mijiedarbības pamatā neatkarīgi no tā, vai tā ietver resursu koplietošanu vai datu apmaiņu. Piemēram, saņēmējam pavedienam vajadzētu piekļūt datiem tikai pēc tam, kad to ir buferējis nosūtīšanas pavediens. Ja saņemošais pavediens piekļuva datiem pirms ievadīšanas buferī, tas ir jāaptur.

Kopīgojot aparatūras resursus, arī sinhronizācija ir absolūti nepieciešama. Ja, piemēram, aktīvam pavedienam ir nepieciešama piekļuve seriālajam portam un cits pavediens, kas pašlaik ir gaidīšanas stāvoklī, strādā ar šo portu ekskluzīvajā režīmā, OS aptur aktīvo pavedienu un neaktivizē to līdz tam nepieciešamajam portam. par brīvu . Bieži vien ir nepieciešama arī sinhronizācija ar notikumiem ārpus datorsistēmas, piemēram, reakcija uz taustiņu kombinācijas Ctrl+C nospiešanu.

Katru sekundi sistēmā notiek simtiem notikumu, kas saistīti ar resursu piešķiršanu un atbrīvošanu, un OS ir jābūt uzticamiem un efektīviem līdzekļiem, kas ļautu sinhronizēt pavedienus ar notikumiem, kas notiek sistēmā.

Lai sinhronizētu lietojumprogrammu pavedienus, programmētājs var izmantot gan savus sinhronizācijas rīkus un paņēmienus, gan operētājsistēmas rīkus. Piemēram, divi viena un tā paša pieteikšanās procesa pavedieni var koordinēt savu darbu, izmantojot globālo Būla mainīgo, kas ir pieejams abiem un kas tiek iestatīts uz vienu, kad notiek kāds notikums, piemēram, viens pavediens rada datus, kas nepieciešami otram, lai turpinātu darbu. Tomēr daudzos gadījumos operētājsistēmas nodrošinātās sinhronizācijas iespējas sistēmas zvanu veidā ir efektīvākas vai pat vienīgās iespējamās. Tādējādi pavedieni, kas pieder pie dažādiem procesiem, nekādi nespēj traucēt viens otra darbu. Bez operētājsistēmas starpniecības viņi nevar apturēt viens otru vai paziņot viens otram par notikumu. Sinhronizācijas rīkus operētājsistēma izmanto ne tikai lietojumprogrammu procesu sinhronizēšanai, bet arī savām iekšējām vajadzībām.

Parasti operētājsistēmu izstrādātāji lietojumprogrammu un sistēmu programmētāju rīcībā nodrošina plašu sinhronizācijas rīku klāstu. Šie rīki var veidot hierarhiju, kad sarežģītāki tiek veidoti uz vienkāršāku rīku bāzes, kā arī būt funkcionāli specializēti, piemēram, rīki viena procesa pavedienu sinhronizēšanai, rīki dažādu procesu pavedienu sinhronizēšanai datu apmaiņas laikā utt. Bieži vien dažādu sistēmas zvanu sinhronizāciju funkcionalitāte pārklājas, tāpēc programmētājs vienas problēmas risināšanai var izmantot vairākus zvanus atkarībā no personīgajām vēlmēm.


Sinhronizācijas un sacensību nepieciešamība

Sinhronizācijas problēmu neievērošana daudzpavedienu sistēmā var izraisīt nepareizu problēmas risinājumu vai pat sistēmas avāriju. Apsveriet, piemēram (4.16. att.) uzdevumu uzturēt noteikta uzņēmuma klientu datubāzi. Katram klientam datu bāzē tiek piešķirts atsevišķs ieraksts, kurā bez citiem laukiem ir lauki Pasūtījums un Maksājums. Programma, kas uztur datu bāzi, ir veidota kā vienots process, kurā ir vairāki pavedieni, tostarp pavediens A, kas ievada datubāzē informāciju par pasūtījumiem, kas saņemti no klientiem, un pavediens B, kas datu bāzē ieraksta informāciju par klientu maksājumiem par rēķiniem. Abi šie pavedieni darbojas kopā kopējā datu bāzes failā, izmantojot vienus un tos pašus algoritmus, kas ietver trīs darbības.

2. Ievadiet jaunu vērtību laukā Pasūtījums (plūsmai A) vai Maksājums (plūsmai B).

3. Atgrieziet modificēto ierakstu datu bāzes failā.

https://pandia.ru/text/78/239/images/image002_238.gif" width="505" height="374 src=">

Rīsi. 4.17. Relatīvā plūsmas ātruma ietekme uz problēmas risināšanas rezultātu

Kritiskā sadaļa

Svarīgs jēdziens pavedienu sinhronizācijā ir programmas “kritiskās sadaļas” jēdziens. Kritiskā sadaļa ir programmas daļa, kuras izpildes rezultāts var neparedzami mainīties, ja ar šo programmas daļu saistītos mainīgos maina citi pavedieni, kamēr šīs daļas izpilde vēl nav pabeigta. Kritiskā sadaļa vienmēr tiek definēta saistībā ar noteiktu kritiski dati ja izmaiņas tiek veiktas nekoordinēti, var rasties nevēlamas blakusparādības. Iepriekšējā piemērā kritiskie dati bija datu bāzes faila ieraksti. Visiem pavedieniem, kas strādā ar kritiskiem datiem, ir jābūt noteiktai kritiskajai sadaļai. Ņemiet vērā, ka dažādos pavedienos kritiskā sadaļa parasti sastāv no dažādām komandu secībām.

Lai novērstu sacensību ietekmi uz kritiskajiem datiem, ir jānodrošina, lai ar šiem datiem saistītajā kritiskajā sadaļā jebkurā laikā būtu tikai viens pavediens. Nav nozīmes tam, vai šis pavediens ir aktīvā vai apturētā stāvoklī. Šo tehniku ​​sauc savstarpēja izslēgšana. Operētājsistēma izmanto dažādus veidus, kā īstenot savstarpēju izslēgšanu. Dažas metodes ir piemērotas savstarpējai izslēgšanai, kad kritiskajā sadaļā nonāk tikai viena procesa pavedieni, savukārt citas var nodrošināt savstarpēju izslēgšanu dažādu procesu pavedieniem.

Vienkāršākais un tajā pašā laikā neefektīvākais veids, kā nodrošināt savstarpēju izslēgšanu, ir operētājsistēmai atļaut pavedienam atspējot jebkādus pārtraukumus, kamēr tas atrodas kritiskajā sadaļā. Tomēr šī metode praktiski netiek izmantota, jo ir bīstami uzticēties lietotāja pavedienam, lai vadītu sistēmu - tas var aizņemt procesoru ilgu laiku, un, ja pavediens avarē kritiskā sadaļā, visa sistēma avarēs, jo pārtraukumi nekad netiks pieļauti.

2. Sinhronizācijas mehānismi.

Bloķējošie mainīgie

Lai sinhronizētu viena procesa pavedienus, lietojumprogrammu programmētājs var izmantot globālo bloķējošie mainīgie. Programmētājs strādā ar šiem mainīgajiem, kuriem visiem procesa pavedieniem ir tieša piekļuve, neizmantojot OS sistēmas izsaukumus.

Tas atspējotu pārtraukumus visā verifikācijas un instalēšanas darbības laikā.

Savstarpējās izslēgšanas īstenošanai iepriekš aprakstītajā veidā ir būtisks trūkums: laikā, kad viens pavediens atrodas kritiskajā sadaļā, cits pavediens, kuram ir nepieciešams tas pats resurss, kuram ir piekļuve procesoram, nepārtraukti aptaujās bloķējošo mainīgo, tērējot procesora laiku. tam piešķirts, ko varētu izmantot, lai izpildītu kādu citu pavedienu. Lai novērstu šo trūkumu, daudzas operētājsistēmas nodrošina īpašus sistēmas izsaukumus darbam ar kritiskajām sadaļām.

Attēlā Attēlā 4.19 parādīts, kā šīs funkcijas īsteno savstarpēju izslēgšanu operētājsistēmā Windows NT. Pirms kritisko datu modificēšanas pavediens izdod EnterCriticalSection() sistēmas izsaukumu. Šis izsaukums vispirms, tāpat kā iepriekšējā gadījumā, veic bloķējošā mainīgā pārbaudi, kas atspoguļo kritiskā resursa stāvokli. Ja sistēmas izsaukums nosaka, ka resurss ir aizņemts (F(D) = 0), atšķirībā no iepriekšējā gadījuma tas neveic ciklisku aptauju, bet gan ievieto pavedienu gaidīšanas stāvoklī (D) un izdara atzīmi, ka šis pavediens jāaktivizē, kad kļūst pieejams attiecīgais resurss. Vītnei, kas šobrīd izmanto šo resursu, pēc iziešanas no kritiskās sadaļas ir jāizpilda LeaveCriticalSectionO sistēmas funkcija, kā rezultātā bloķējošais mainīgais iegūst vērtību, kas atbilst resursa brīvajam stāvoklim (F(D) = 1), un operētājsistēma izskata rindu tiem, kas gaida šī resursa pavedienus, un pārvieto pirmo pavedienu no rindas uz gatavības stāvokli.

Pieskaitāmās izmaksas" href="/text/category/nakladnie_rashodi/" rel="bookmark">OS pieskaitāmās izmaksas par kritiskās sadaļas ieiešanas un iziešanas funkcijas ieviešanu var pārsniegt iegūtos ietaupījumus.

Semafori

Bloķējošo mainīgo vispārinājums ir tā sauktais Dijkstra semafori. Bināro mainīgo vietā Dijkstra ierosināja izmantot mainīgos, kas var iegūt nenegatīvas veselas vērtības. Šādus mainīgos, ko izmanto skaitļošanas procesu sinhronizēšanai, sauc par semaforiem.

Lai strādātu ar semaforiem, tiek ieviesti divi primitīvi, tradicionāli apzīmēti ar P un V. Lai mainīgais S attēlo semaforu. Tad darbības V(S) un P(S) tiek definētas šādi.

* V(S): mainīgais S tiek palielināts par 1, veicot vienu darbību. Paraugu ņemšanu, veidošanu un uzglabāšanu nevar pārtraukt. Šīs darbības laikā citi pavedieni nepiekļūst mainīgajam S.

* P(S): samazina S par 1, ja iespējams. Ja 5=0 un nav iespējams samazināt S, paliekot nenegatīvu veselu skaitļu vērtību apgabalā, tad pavedienu izsaukšanas operācija P gaida, līdz šī samazināšana kļūst iespējama. Veiksmīga pārbaude un samazināšana arī ir nedalāma darbība.

V un P primitīvu izpildes laikā nav pieļaujami nekādi pārtraukumi.

Īpašā gadījumā, kad semafors S var iegūt tikai vērtības 0 un 1, tas kļūst par bloķējošo mainīgo, ko šī iemesla dēļ bieži sauc par bināro semaforu. Aktivitātei P ir iespēja iestatīt pavedienu, kas to izpilda, gaidīšanas stāvoklī, savukārt darbība V noteiktos apstākļos var pamodināt citu pavedienu, kas tika apturēts ar darbību P.

Apskatīsim semaforu izmantošanu, izmantojot klasisku piemēru divu pavedienu mijiedarbībai, kas darbojas daudzprogrammēšanas režīmā, no kuriem viens ieraksta datus bufera pūlā, bet otrs nolasa tos no bufera pūla. Ļaujiet bufera pūlam sastāvēt no N buferiem, no kuriem katrā var būt viens ieraksts. Parasti rakstīšanas pavedienam un lasītāja pavedienam var būt atšķirīgs ātrums un tie var piekļūt bufera pūlam ar dažādu intensitāti. Vienā periodā rakstīšanas ātrums var pārsniegt lasīšanas ātrumu, citā - otrādi. Lai pareizi darbotos kopā, rakstīšanas pavedienam ir jāaptur, kad visi buferi ir aizņemti, un jāaktivizē, kad ir atbrīvots vismaz viens buferis. Turpretim lasītāja pavedienam vajadzētu pauzēt, kad visi buferi ir tukši, un pamosties, kad parādās vismaz viens ieraksts.

Ieviesīsim divus semaforus: e - tukšo buferu skaits, un f - aizpildīto buferu skaits, un sākotnējā stāvoklī e = N, a f = 0. Tad pavedienu darbību ar kopēju buferu baseinu var aprakstīt šādi. (4.20. att.).

Rakstītāja pavediens vispirms veic P(e) darbību, ar kuru tā pārbauda, ​​vai bufera pūlā nav tukšu buferu. Saskaņā ar P darbības semantiku, ja semafors e ir vienāds ar 0 (tas ir, šobrīd nav brīvu buferu), tad rakstīšanas pavediens nonāk gaidīšanas stāvoklī. Ja e vērtība ir pozitīvs skaitlis, tad tas samazina brīvo buferu skaitu, ieraksta datus nākamajā brīvajā buferī un pēc tam palielina aizņemto buferu skaitu ar operāciju V(f). Lasītāja pavediens darbojas līdzīgi, ar atšķirību, ka tas sākas ar pilnu buferu pārbaudi, un pēc datu nolasīšanas palielina brīvo buferu skaitu.

DIV_ADBLOCK860">

Semaforu var izmantot arī kā bloķējošu mainīgo. Iepriekš apskatītajā piemērā, lai novērstu sadursmes, strādājot ar koplietojamo atmiņas apgabalu, mēs pieņemsim, ka rakstīšana buferī un lasīšana no tā ir kritiskas sadaļas. Savstarpēju izslēgšanu nodrošināsim, izmantojot bināro semaforu b (4.21. att.). Abiem pavedieniem pēc buferu pieejamības pārbaudes ir jāpārbauda kritiskās sadaļas pieejamība.

https://pandia.ru/text/78/239/images/image007_110.jpg" width="495" height="639 src=">

Rīsi. 4.22. Strupceļu rašanās programmas izpildes laikā

PIEZĪME

Strupceļi ir jānošķir no vienkāršām rindām, lai gan abas rodas, kad resursi tiek koplietoti, un izskatās līdzīgi: pavediens tiek apturēts un gaida, kad resurss atbrīvosies. Tomēr rinda ir normāla parādība, kas liecina par augstu resursu izmantošanu, kad pieprasījumi tiek saņemti nejauši. Rinda parādās, ja resurss šobrīd nav pieejams, bet pēc kāda laika tiks atbrīvots, ļaujot pavedienam turpināt izpildi. Strupceļš, kā norāda tās nosaukums, ir nedaudz neatrisināma situācija. Nepieciešams nosacījums, lai strupceļš notiktu, ir tas, ka pavedienam ir nepieciešami vairāki resursi vienlaikus.

Aplūkotajos piemēros strupceļu veidoja divi pavedieni, bet vairāki pavedieni var savstarpēji bloķēt viens otru. Attēlā 2.23. attēlā parādīts šāds resursu Ri sadalījums starp vairākiem pavedieniem Tj, kas noveda pie strupceļu rašanās. Bultiņas norāda plūsmas resursu prasības. Cietā bultiņa nozīmē, ka pavedienam ir piešķirts atbilstošais resurss, un punktēta bultiņa savieno pavedienu ar nepieciešamo resursu, bet to vēl nevar piešķirt, jo to aizņem cits pavediens. Piemēram, pavedienam T1 ir nepieciešami resursi R1 un R2, lai veiktu darbu, no kuriem tiek piešķirts tikai viens - R1, bet resurss R2 tiek turēts pavedienā T2. Neviens no četriem attēlā redzamajiem pavedieniem nevar turpināt savu darbu, jo viņiem nav visu nepieciešamo resursu.

Pavedienu nespēja pabeigt iesākto darbu strupceļu dēļ samazina skaitļošanas sistēmas veiktspēju. Tāpēc liela uzmanība tiek pievērsta strupceļu novēršanas problēmai. Gadījumā, ja notiek strupceļš, sistēmai ir jānodrošina operatora operatoram līdzeklis, ar kuru viņš var atpazīt strupceļu un atšķirt to no parastā blokādes īslaicīgas resursu nepieejamības dēļ. Visbeidzot, ja tiek diagnosticēts strupceļš, ir nepieciešami līdzekļi, lai novērstu strupceļu un atjaunotu normālu skaitļošanas procesu.

Īpašnieks" href="/text/category/vladeletc/" rel="bookmark">īpašnieks, iestatot to bezsignāla stāvoklī, un nonāk kritiskajā sadaļā. Kad pavediens ir pabeidzis darbu ar kritiskajiem datiem, tas "dod uz augšu” mutex, iestatot to signalizētā stāvoklī. Šobrīd mutex ir brīvs un nepieder nevienam pavedienam. Ja kāds pavediens gaida tā atbrīvošanu, tad tas kļūst par nākamo šī mutex īpašnieku, plkst. tajā pašā laikā mutex pāriet nesignalizētā stāvoklī.

Notikuma objekts (šajā gadījumā vārds "notikums" tiek lietots šaurā nozīmē, kā noteikta veida sinhronizācijas objekta apzīmējums) parasti tiek izmantots nevis, lai piekļūtu datiem, bet lai paziņotu citiem pavedieniem, ka dažas darbības ir pabeigtas. Ļaujiet, piemēram, kādā lietojumprogrammā darbs organizēts tā, ka viens pavediens nolasa datus no faila atmiņas buferī, bet citi pavedieni apstrādā šos datus, tad pirmais pavediens nolasa jaunu datu daļu un citi pavedieni. apstrādājiet to vēlreiz un tā tālāk. Izpildes sākumā pirmais pavediens iestata notikuma objekta stāvokli bez signāla. Visi pārējie pavedieni ir veikuši izsaukumu uz Wait(X), kur X ir notikuma rādītājs, un tie ir apturētā stāvoklī, gaidot šī notikuma iestāšanos. Tiklīdz buferis ir pilns, pirmais pavediens ziņo par to operētājsistēmai, izsaucot Set(X). Operētājsistēma skenē gaidošo pavedienu rindu un aktivizē visus pavedienus, kas gaida šo notikumu.

Signāli

SignālsĻauj uzdevumam reaģēt uz notikumu, kura avots var būt operētājsistēma vai cits uzdevums. Signāli ietver uzdevuma pārtraukšanu un iepriekš noteiktu darbību izpildi. Signālus var ģenerēt sinhroni, tas ir, paša procesa darba rezultātā, vai arī tos var nosūtīt uz procesu ar citu procesu, tas ir, ģenerēt asinhroni. Sinhronie signāli visbiežāk nāk no procesora pārtraukumu sistēmas un norāda uz procesa darbībām, kuras bloķē aparatūra, piemēram, dalīšanu ar nulli, adresācijas kļūdu, atmiņas aizsardzības pārkāpumu utt.

Asinhronā signāla piemērs ir signāls no termināļa. Daudzas operētājsistēmas nodrošina tūlītēju procesa noņemšanu no izpildes. Lai to izdarītu, lietotājs var nospiest noteiktu taustiņu kombināciju (Ctrl+C, Ctrl+Break), kā rezultātā OS ģenerē signālu un nosūta to aktīvajam procesam. Signāls var nonākt jebkurā procesa izpildes laikā (tas ir, tas ir asinhrons), tāpēc process nekavējoties jāpārtrauc. Šajā gadījumā reakcija uz signālu ir procesa beznosacījuma pabeigšana.

Sistēmā var definēt signālu kopu. Procesa, kas saņēma signālu, programmas kods var to ignorēt vai atbildēt uz to ar standarta darbību (piemēram, iziet), vai veikt noteiktas darbības, ko definējis lietojumprogrammas programmētājs. Pēdējā gadījumā programmas kodā ir jāparedz speciāli sistēmas izsaukumi, ar kuru palīdzību operētājsistēma tiek informēta, kāda procedūra jāveic, reaģējot uz konkrēta signāla saņemšanu.

Signāli nodrošina loģisku saziņu starp procesiem un starp procesiem un lietotājiem (termināli). Tā kā signāla nosūtīšanai ir nepieciešamas procesa identifikatora zināšanas, mijiedarbība caur signāliem iespējama tikai starp saistītiem procesiem, kuri var iegūt informāciju par otra identifikatoriem.

Sadalītās sistēmās, kas sastāv no vairākiem procesoriem, kuriem katram ir sava RAM, bloķēšanas mainīgie, semafori, signāli un citas līdzīgas koplietojamās atmiņas funkcijas nav piemērotas. Šādās sistēmās sinhronizāciju var panākt tikai ar ziņojumu apmaiņu.

Process ir atmiņā ielādētas programmas gadījums. Šis gadījums var izveidot pavedienus, kas ir izpildāmu instrukciju secība. Ir svarīgi saprast, ka nedarbojas procesi, bet gan pavedieni.

Turklāt jebkuram procesam ir vismaz viens pavediens. Šo pavedienu sauc par lietojumprogrammas galveno (galveno) pavedienu.

Tā kā gandrīz vienmēr ir daudz vairāk pavedienu, nekā ir fiziski procesori, lai tos izpildītu, pavedieni faktiski netiek izpildīti vienlaicīgi, bet savukārt (procesora laiks tiek sadalīts starp pavedieniem). Taču pārslēgšanās starp tām notiek tik bieži, ka šķiet, ka tās darbojas paralēli.

Atkarībā no situācijas pavedieni var būt trīs stāvokļos. Pirmkārt, pavedienu var izpildīt, kad tam ir piešķirts CPU laiks, t.i. tas var būt darbības stāvoklī. Otrkārt, tas var būt neaktīvs un gaida, kad procesors tiks piešķirts, t.i. būt gatavības stāvoklī. Un ir vēl trešais, arī ļoti svarīgs stāvoklis – bloķējošais stāvoklis. Ja pavediens ir bloķēts, tam vispār netiek piešķirts laiks. Parasti bloks tiek novietots, gaidot kādu notikumu. Kad notiek šis notikums, pavediens tiek automātiski pārvietots no bloķēta stāvokļa uz gatavības stāvokli. Piemēram, ja viens pavediens veic aprēķinus, bet otram jāgaida, līdz rezultāti tiks saglabāti diskā. Otrajā varētu izmantot cilpu, piemēram, "while(!isCalcFinished) turpināt;", taču praksē ir viegli pārbaudīt, vai šīs cilpas izpildes laikā procesors ir 100% aizņemts (to sauc par aktīvo gaidīšanu). Ja iespējams, ir jāizvairās no šādiem cikliem, kuros bloķēšanas mehānisms sniedz nenovērtējamu palīdzību. Otrais pavediens var bloķēt sevi, līdz pirmais pavediens rada notikumu, kas norāda, ka lasīšana ir pabeigta.

Pavedienu sinhronizēšana operētājsistēmā Windows OS

Windows ievieš preemptīvu daudzuzdevumu veikšanu - tas nozīmē, ka sistēma jebkurā laikā var pārtraukt viena pavediena izpildi un pārsūtīt vadību citam. Iepriekš operētājsistēmā Windows 3.1 tika izmantota organizēšanas metode, ko sauca par kooperatīvo daudzuzdevumu veikšanu: sistēma gaidīja, līdz pavediens pats nodod tai vadību, un tāpēc, ja viena lietojumprogramma sastinga, dators bija jāpārstartē.

Visiem pavedieniem, kas pieder vienam procesam, ir kopīgi resursi, piemēram, RAM adrešu telpa vai atvērtie faili. Šie resursi pieder visam procesam un līdz ar to arī katram tā pavedienam. Tāpēc katrs pavediens var strādāt ar šiem resursiem bez ierobežojumiem. Bet... Ja viens pavediens vēl nav pabeidzis darbu ar kādu koplietotu resursu, un sistēma pārslēdzas uz citu pavedienu, izmantojot to pašu resursu, tad šo pavedienu darba rezultāts var krasi atšķirties no iecerētā. Šādi konflikti var rasties arī starp pavedieniem, kas pieder pie dažādiem procesiem. Ikreiz, kad diviem vai vairākiem pavedieniem tiek koplietots kāds koplietots resurss, rodas šī problēma.

Piemērs. Ārpus sinhronizācijas pavedieni: ja īslaicīgi apturat displeja pavedienu (pauze), fona masīva populācijas pavediens turpinās darboties.

#iekļauts #iekļauts int a; ROKTURIS hThr; neparakstīts garš uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( for (i=0; i<5; i++) a[i] = num; num++; } } int main(void) { hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) printf("%d %d %d %d %d\n", a, a, a, a, a); return 0; }

Tāpēc ir nepieciešams mehānisms, kas ļautu pavedieniem koordinēt savu darbu ar kopīgiem resursiem. Šo mehānismu sauc par pavedienu sinhronizācijas mehānismu.

Šis mehānisms ir operētājsistēmas objektu kopa, kas tiek izveidota un pārvaldīta programmatiski, ir kopīgi visiem sistēmas pavedieniem (daži no tiem tiek koplietoti ar pavedieniem, kas pieder vienam procesam), un tiek izmantoti, lai koordinētu piekļuvi resursiem. Resursi var būt jebkas, ko var koplietot divi vai vairāki pavedieni – diska fails, ports, datu bāzes ieraksts, GDI objekts un pat globāls programmas mainīgais (kam var piekļūt pavedieni, kas pieder vienam un tam pašam procesam).

Ir vairāki sinhronizācijas objekti, no kuriem svarīgākie ir mutex, kritiskā sadaļa, notikums un semafors. Katrs no šiem objektiem īsteno savu sinhronizācijas metodi. Arī pašus procesus un pavedienus var izmantot kā sinhronizācijas objektus (kad viens pavediens gaida cita pavediena vai procesa pabeigšanu); kā arī failus, sakaru ierīces, konsoles ievadi un paziņojumus par izmaiņām.

Jebkurš sinhronizācijas objekts var būt tā sauktajā signāla stāvoklī. Katram objekta veidam šim stāvoklim ir atšķirīga nozīme. Pavedieni var pārbaudīt objekta pašreizējo stāvokli un/vai gaidīt izmaiņas šajā stāvoklī un tādējādi koordinēt savas darbības. Tas nodrošina, ka tad, kad pavediens strādā ar sinhronizācijas objektiem (tos izveido, maina stāvokli), sistēma nepārtrauks tā izpildi, kamēr tā nepabeigs šo darbību. Tādējādi visas pēdējās darbības ar sinhronizācijas objektiem ir atomāras (nedalāmas.

Darbs ar sinhronizācijas objektiem

Lai izveidotu vienu vai otru sinhronizācijas objektu, tiek izsaukta īpaša WinAPI funkcija ar veidu Create... (piemēram, CreateMutex). Šis izsaukums atgriež rokturi objektam (HANDLE), ko var izmantot visi šim procesam piederošie pavedieni. Sinhronizācijas objektam ir iespējams piekļūt no cita procesa – vai nu mantojot šim objektam rokturi, vai, vēlams, izmantojot objekta atvēršanas funkcijas izsaukumu (Open...). Pēc šī zvana process saņems rokturi, ko vēlāk varēs izmantot darbam ar objektu. Objektam, ja vien tas nav paredzēts lietošanai vienā procesā, ir jāpiešķir nosaukums. Visu objektu nosaukumiem ir jābūt atšķirīgiem (pat ja tie ir dažāda veida). Piemēram, jūs nevarat izveidot notikumu un semaforu ar tādu pašu nosaukumu.

Izmantojot esošo objekta deskriptoru, varat noteikt tā pašreizējo stāvokli. Tas tiek darīts, izmantojot t.s. gaidošās funkcijas. Visbiežāk izmantotā funkcija ir WaitForSingleObject. Šai funkcijai ir nepieciešami divi parametri, no kuriem pirmais ir objekta rokturis, otrais ir taimauts ms. Funkcija atgriež WAIT_OBJECT_0, ja objekts tiek signalizēts, WAIT_TIMEOUT, ja tam iestājās taimauts, un WAIT_ABANDONED, ja mutex objekts netika atbrīvots pirms tam piederošā pavediena iziešanas. Ja taimauts ir norādīts kā nulle, funkcija nekavējoties atgriež rezultātu, pretējā gadījumā tā gaida norādīto laiku. Ja objekta stāvoklis kļūst par signālu pirms šī laika beigām, funkcija atgriezīs WAIT_OBJECT_0, pretējā gadījumā funkcija atgriezīs WAIT_TIMEOUT. Ja simboliskā konstante INFINITE ir norādīta kā laiks, funkcija gaidīs bezgalīgi, līdz objekta stāvoklis kļūs par signālu.

Ļoti svarīgs fakts ir tas, ka gaidīšanas funkcijas izsaukšana bloķē pašreizējo pavedienu, t.i. Kamēr pavediens atrodas dīkstāves stāvoklī, tam netiek piešķirts CPU laiks.

Kritiskās sadaļas

Kritiskās sadaļas objekts palīdz programmētājam izolēt koda sadaļu, kurā pavediens piekļūst koplietotajam resursam, un novērš vienlaicīgu resursa izmantošanu. Pirms resursa izmantošanas pavediens nonāk kritiskajā sadaļā (izsauc funkciju EnterCriticalSection). Ja kāds cits pavediens mēģina ievadīt to pašu kritisko sadaļu, tā izpilde tiks apturēta, līdz pirmais pavediens atstāj sadaļu, izsaucot LeaveCriticalSection. Izmanto tikai viena procesa pavedieniem. Kritiskās sadaļas ievadīšanas secība nav noteikta.

Ir arī funkcija TryEnterCriticalSection, kas pārbauda, ​​vai kritiskā sadaļa pašlaik ir aizņemta. Ar tās palīdzību pavedienu, gaidot piekļuvi resursam, nevar bloķēt, bet veikt dažas noderīgas darbības.

Piemērs. Pavedienu sinhronizēšana, izmantojot kritiskās sadaļas.

#iekļauts #iekļauts CRITICAL_SECTION cs; int a; ROKTURIS hThr; neparakstīts garš uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( EnterCriticalSection(&cs); for (i=0; i<5; i++) a[i] = num; num++; LeaveCriticalSection(&cs); } } int main(void) { InitializeCriticalSection(&cs); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { EnterCriticalSection(&cs); printf("%d %d %d %d %d\n", a, a, a, a, a); LeaveCriticalSection(&cs); } return 0; }

Savstarpēji izņēmumi

Savstarpējās izslēgšanas objekti (mutexes, mutex - no MUTual EXclusion) ļauj koordinēt savstarpēju piekļuves izslēgšanu koplietotam resursam. Objekta signāla stāvoklis (t.i., "iestatītais" stāvoklis) atbilst brīdim, kad objekts nepieder nevienam pavedienam un to var "tvert". Un otrādi, "atiestatīšanas" (bez signāla) stāvoklis atbilst brīdim, kad kādam pavedienam jau pieder šis objekts. Piekļuve objektam tiek piešķirta, kad pavediens, kuram objekts pieder, to atbrīvo.

Divi (vai vairāki) pavedieni var izveidot mutex ar tādu pašu nosaukumu, izsaucot funkciju CreateMutex. Pirmais pavediens faktiski izveido mutex, un nākamie iegūst rokturi jau esošam objektam. Tas ļauj vairākiem pavedieniem iegūt rokturi vienam un tam pašam mutex, atbrīvojot programmētāju no nepieciešamības uztraukties par to, kurš faktiski izveido mutex. Ja tiek izmantota šī pieeja, ir ieteicams iestatīt karodziņu bInitialOwner uz FALSE, pretējā gadījumā radīsies zināmas grūtības noteikt faktisko mutex veidotāju.

Vairāki pavedieni var iegūt rokturi vienam un tam pašam mutexam, nodrošinot starpprocesu saziņu. Šai pieejai var izmantot šādus mehānismus:

  • Pakārtots process, kas izveidots, izmantojot funkciju CreateProcess, var mantot mutex turi, ja, veidojot mutex ar funkciju CreateMutex, tika norādīts parametrs lpMutexAttributes.
  • Pavediens var iegūt esoša mutex dublikātu, izmantojot funkciju DuplicateHandle.
  • Pavediens var norādīt esošā mutex nosaukumu, izsaucot OpenMutex vai CreateMutex funkcijas.

Lai paziņotu, ka savstarpēja izslēgšana pieder pašreizējam pavedienam, jums ir jāizsauc kāda no gaidīšanas funkcijām. Pavediens, kuram pieder objekts, var to atkārtoti iegūt tik reižu, cik vēlas (tas neizraisīs pašbloķēšanos), taču tam tas ir jāatlaiž tikpat reižu, izmantojot funkciju ReleaseMutex.

Lai sinhronizētu viena procesa pavedienus, efektīvāk ir izmantot kritiskās sadaļas.

Piemērs. Pavedienu sinhronizēšana, izmantojot mutexes.

#iekļauts #iekļauts ROKTURIS hMutex; int a; ROKTURIS hThr; neparakstīts garš uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hMutex, INFINITE); for (i=0; i<5; i++) a[i] = num; num++; ReleaseMutex(hMutex); } } int main(void) { hMutex=CreateMutex(NULL, FALSE, NULL); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hMutex, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); ReleaseMutex(hMutex); } return 0; }

Pasākumi

Notikumu objekti tiek izmantoti, lai paziņotu gaidīšanas pavedieniem, ka ir noticis notikums. Ir divu veidu notikumi – ar manuālu un automātisku atiestatīšanu. Manuālo atiestatīšanu veic funkcija ResetEvent. Manuālās atiestatīšanas notikumi tiek izmantoti, lai vienlaikus paziņotu vairākiem pavedieniem. Izmantojot automātiskās atiestatīšanas notikumu, tikai viens gaidošais pavediens saņems paziņojumu un turpinās izpildi; pārējie turpinās gaidīt.

Funkcija CreateEvent izveido notikuma objektu, SetEvent - iestata notikumu signāla stāvoklī, ResetEvent - atiestata notikumu. Funkcija PulseEvent iestata notikumu, un pēc tam, kad tiek atsākti pavedieni, kas gaida šo notikumu (visi ar manuālu atiestatīšanu un tikai viens ar automātisku atiestatīšanu), tā to atiestata. Ja gaidīšanas pavedienu nav, PulseEvent vienkārši atiestata notikumu.

Piemērs. Pavedienu sinhronizēšana, izmantojot notikumus.

#iekļauts #iekļauts APSTRĀDĀT hEvent1, hEvent2; int a; ROKTURIS hThr; neparakstīts garš uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hEvent2, INFINITE); for (i=0; i<5; i++) a[i] = num; num++; SetEvent(hEvent1); } } int main(void) { hEvent1=CreateEvent(NULL, FALSE, TRUE, NULL); hEvent2=CreateEvent(NULL, FALSE, FALSE, NULL); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hEvent1, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); SetEvent(hEvent2); } return 0; }

Semafori

Semafora objekts faktiski ir mutex objekts ar skaitītāju. Šis objekts ļauj sevi “uztvert” noteiktam pavedienu skaitam. Pēc tam “tvert” nebūs iespējams, kamēr viens no pavedieniem, kas iepriekš “uztvēra” semaforu, to neatbrīvos. Semafori tiek izmantoti, lai ierobežotu pavedienu skaitu, kas vienlaikus strādā ar resursu. Maksimālais pavedienu skaits tiek pārsūtīts uz objektu inicializācijas laikā; pēc katras “tveršanas” semaforu skaitītājs tiek samazināts. Signāla stāvoklis atbilst skaitītāja vērtībai, kas ir lielāka par nulli. Ja skaitītājs ir nulle, tiek uzskatīts, ka semafors nav instalēts (atiestatīts).

Funkcija CreateSemaphore izveido semafora objektu, norādot tā maksimālo iespējamo sākotnējo vērtību, OpenSemaphore - atgriež esoša semafora deskriptoru, semafors tiek uztverts, izmantojot gaidīšanas funkcijas, un semafora vērtība tiek samazināta par vienu, ReleaseSemaphore - semafors tiek atbrīvots kopā ar semaforu. vērtība palielināta par parametra numurā norādīto vērtību.

Piemērs. Pavedienu sinhronizēšana, izmantojot semaforus.

#iekļauts #iekļauts ROKTURIS hSem; int a; ROKTURIS hThr; neparakstīts garš uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hSem, INFINITE); for (i=0; i<5; i++) a[i] = num; num++; ReleaseSemaphore(hSem, 1, NULL); } } int main(void) { hSem=CreateSemaphore(NULL, 1, 1, "MySemaphore1"); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hSem, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); ReleaseSemaphore(hSem, 1, NULL); } return 0; }

Aizsargāta piekļuve mainīgajiem lielumiem

Ir vairākas funkcijas, kas ļauj strādāt ar globālajiem mainīgajiem no visiem pavedieniem, neuztraucoties par sinhronizāciju, jo šīs funkcijas pašas to uzrauga - to izpilde ir atomāra. Šīs funkcijas ir InterlockedIncrement, InterlockedDecrement, InterlockedExchange, InterlockedExchangeAdd un InterlockedCompareExchange. Piemēram, funkcija InterlockedIncrement atomiski palielina 32 bitu mainīgā vērtību par vienu, ko ir ērti izmantot dažādiem skaitītājiem.

Lai iegūtu pilnīgu informāciju par visu WIN32 API funkciju mērķi, lietošanu un sintaksi, jums jāizmanto MS SDK palīdzības sistēma, kas iekļauta Borland Delphi vai CBuilder programmēšanas vidēs, kā arī MSDN, kas tiek piegādāts kā Visual C programmēšanas sistēmas daļa.

Pavedieni var būt vienā no vairākiem stāvokļiem:

    Gatavs(gatavs) – atrodas diegu baseinā, kas gaida izpildi;

    Skriešana(izpilde) - darbojas procesorā;

    Gaida(gaida), saukta arī par dīkstāvi vai apturēta, apturēta - gaidīšanas stāvoklī, kas beidzas ar pavediena izpildes sākšanu (Running state) vai ieiešanu stāvoklī Gatavs;

    Izbeigts (pabeigšana) - visu pavedienu komandu izpilde ir pabeigta. Vēlāk to var izdzēst. Ja straume netiek dzēsta, sistēma var atiestatīt to sākotnējā stāvoklī vēlākai lietošanai.

Pavedienu sinhronizācija

Darbīgajiem pavedieniem bieži ir kaut kādā veidā jāsazinās. Piemēram, ja vairāki pavedieni mēģina piekļūt dažiem globāliem datiem, katram pavedienam ir jāaizsargā dati, lai tos nevarētu mainīt cits pavediens. Dažreiz vienam pavedienam ir jāzina, kad cits pavediens pabeigs uzdevumu. Šāda mijiedarbība ir obligāta gan starp vienu, gan dažādu procesu pavedieniem.

pavedienu sinhronizācija ( pavediens sinhronizācija) ir vispārīgs termins, kas attiecas uz pavedienu mijiedarbības un savstarpējās savienošanas procesu. Lūdzu, ņemiet vērā, ka pavedienu sinhronizēšanai ir nepieciešams, lai pati operētājsistēma darbotos kā starpnieks. Pavedieni nevar mijiedarboties viens ar otru bez viņas līdzdalības.

Programmā Win32 ir vairākas pavedienu sinhronizēšanas metodes. Gadās, ka konkrētā situācijā viena metode ir labāka par citu. Īsi apskatīsim šīs metodes.

Kritiskās sadaļas

Viena no pavedienu sinhronizēšanas metodēm ir kritisko sadaļu izmantošana. Šī ir vienīgā pavedienu sinhronizācijas metode, kurai nav nepieciešams Windows kodols. (Kritiskā sadaļa nav kodola objekts.) Tomēr šo metodi var izmantot tikai viena procesa pavedienu sinhronizēšanai.

Kritiskā sadaļa ir koda sadaļa, kuru vienlaikus var izpildīt tikai viens pavediens. Ja kods, ko izmanto masīva inicializācijai, tiek ievietots kritiskā sadaļā, citi pavedieni nevarēs ievadīt šo koda sadaļu, kamēr pirmais pavediens nebūs pabeidzis to izpildīt.

Pirms kritiskās sadaļas izmantošanas tā ir jāinicializē, izmantojot Win32 API procedūru InitializeCriticalSection(), kas (delphi) ir definēta šādi:

procedūra InitializeCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

Parametrs IpCriticalSection ir TRTLCriticalSection tipa ieraksts, kas tiek nodots ar atsauci. Precīzai TRTLCriticalSection ieraksta definīcijai nav lielas nozīmes, jo diez vai jums kādreiz vajadzēs apskatīt tā saturu. Viss, kas jums jādara, ir jānodod neinicializēts ieraksts parametram IpCtitical Section, un šis ieraksts tiks nekavējoties aizpildīts ar procedūru.

Pēc ieraksta aizpildīšanas programmā varat izveidot kritisku sadaļu, ievietojot daļu tās teksta starp funkciju EnterCriticalSection() un LeaveCriticalSection() izsaukumiem. Šīs procedūras ir definētas šādi:

procedūra EnterCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

procedūra LeaveCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

Parametrs IpCriticalSection, kas tiek nodots šīm procedūrām, nav nekas cits kā ieraksts, kas izveidots ar procedūru InitializeCriticalSection().

Funkcija EnterCriticalSection pārbauda, ​​vai kāds cits pavediens jau izpilda savas programmas kritisko sadaļu, kas saistīta ar doto kritiskās sadaļas objektu. Ja nē, pavediens saņem atļauju izpildīt savu kritisko kodu, vai drīzāk, tas netiek liegts to darīt. Ja tā, tad pavediens, kas veic pieprasījumu, tiek iestatīts gaidīšanas stāvoklī, un pieprasījums tiek reģistrēts. Tā kā ieraksti ir jāizveido, kritiskās sadaļas objekts ir datu struktūra.

Kad funkcija Atstājiet Kritisko sadaļu ko izsauc pavediens, kuram pašlaik ir atļauja izpildīt savu kritisko koda sadaļu, kas saistīta ar doto kritiskās sadaļas objektu, sistēma var pārbaudīt, vai rindā nav vēl kāds pavediens, kas gaida šī objekta atbrīvošanu. Pēc tam sistēma var noņemt gaidīšanas pavedienu no gaidīšanas stāvokļa, un tā turpinās savu darbu (tai piešķirtajā laika daļā).

Kad esat pabeidzis darbu ar ierakstu TRTLCriticalSection, tas ir jāatbrīvo, izsaucot DeleteCriticalSection() procedūru, kas tiek definēta šādi:

procedūra DeleteCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

Dažreiz, strādājot ar vairākiem pavedieniem vai procesiem, tas kļūst nepieciešams sinhronizēt izpildi divi vai vairāki no tiem. Iemesls tam visbiežāk ir tas, ka diviem vai vairākiem pavedieniem var būt nepieciešama piekļuve koplietotam resursam, kas tiešām nevar nodrošināt vairākiem pavedieniem vienlaikus. Koplietots resurss ir resurss, kuram vienlaikus var piekļūt vairāki izpildāmi uzdevumi.

Tiek saukts mehānisms, kas nodrošina sinhronizācijas procesu piekļuves ierobežojums. Nepieciešamība pēc tā rodas arī gadījumos, kad viens pavediens gaida kāda cita pavediena ģenerētu notikumu. Protams, ir jābūt kādam veidam, kā pirmais pavediens tiks apturēts pirms notikuma. Pēc tam pavedienam jāturpina tā izpilde.

Ir divi vispārīgi stāvokļi, kuros uzdevums var būt. Pirmkārt, uzdevums var jāveic(vai esiet gatavs izpildei, tiklīdz tas iegūst piekļuvi CPU resursiem). Otrkārt, uzdevums var būt bloķēts.Šajā gadījumā tā izpilde tiek apturēta, līdz tiek atbrīvots nepieciešamais resurss vai notiek noteikts notikums.

Windows ir speciāli servisi, kas ļauj noteiktā veidā ierobežot piekļuvi koplietotajiem resursiem, jo ​​bez operētājsistēmas palīdzības atsevišķs process vai pavediens nevar pats noteikt, vai tam ir vienīgā pieeja kādam resursam. Operētājsistēma Windows satur procedūru, kas vienas nepārtrauktas darbības laikā pārbauda un, ja iespējams, iestata resursa piekļuves karogu. Operētājsistēmu izstrādātāju valodā šo darbību sauc pārbaude un uzstādīšanas darbība. Tiek izsaukti karodziņi, ko izmanto, lai nodrošinātu sinhronizāciju un kontrolētu piekļuvi resursiem semafori(semafors). Win32 API nodrošina atbalstu semaforiem un citiem sinhronizācijas objektiem. MFC bibliotēkā ir arī atbalsts šiem objektiem.

Sinhronizācijas objekti un mfc klases

Win32 interfeiss atbalsta četru veidu sinhronizācijas objektus – tie visi kaut kādā veidā ir balstīti uz semafora jēdzienu.

Pirmais objektu veids ir pats semafors vai klasiskais (standarta) semafors. Tas ļauj ierobežotam skaitam procesu un pavedienu piekļūt vienam resursam. Šajā gadījumā piekļuve resursam ir vai nu pilnībā ierobežota (noteiktā laika periodā resursam var piekļūt tikai viens pavediens vai process), vai arī vienlaicīgu piekļuvi saņem tikai neliels skaits pavedienu un procesu. Semafori tiek ieviesti, izmantojot skaitītāju, kura vērtība samazinās, kad uzdevumam tiek piešķirts semafors, un palielinās, kad uzdevums atbrīvo semaforu.

Otrs sinhronizācijas objektu veids ir ekskluzīvs (mutex) semafors. Tas ir paredzēts, lai pilnībā ierobežotu piekļuvi resursam, lai resursam jebkurā laikā varētu piekļūt tikai viens process vai pavediens. Patiesībā šis ir īpašs semafora veids.

Trešais sinhronizācijas objektu veids ir notikumu, vai notikuma objekts. To izmanto, lai bloķētu piekļuvi resursam, līdz kāds cits process vai pavediens paziņo, ka resursu var izmantot. Tādējādi šis objekts signalizē par vajadzīgā notikuma pabeigšanu.

Izmantojot ceturtā tipa sinhronizācijas objektu, jūs varat aizliegt noteiktu programmas koda sadaļu izpildi ar vairākiem pavedieniem vienlaikus. Lai to izdarītu, šīs platības ir jādeklarē kā kritiskā sadaļa. Kad viens pavediens nonāk šajā sadaļā, citiem pavedieniem ir aizliegts darīt to pašu, līdz pirmais pavediens iziet no sadaļas.

Atšķirībā no cita veida sinhronizācijas objektiem kritiskās sadaļas tiek izmantotas tikai pavedienu sinhronizēšanai viena un tā paša procesa ietvaros. Cita veida objektus var izmantot, lai sinhronizētu pavedienus procesā vai sinhronizētu procesus.

Programmā MFC Win32 saskarnes nodrošināto sinhronizācijas mehānismu atbalsta šādas klases, kas ir atvasinātas no CSyncObject klases:

    CCritiskā sadaļa- īsteno kritisko sadaļu.

    CEpasākums- realizē notikuma objektu

    CMutex- īsteno ekskluzīvu semaforu.

    Cemafors- īsteno klasisko semaforu.

Papildus šīm klasēm MFC definē arī divas papildu sinhronizācijas klases: CSingleLock Un CMultiLock. Tie kontrolē piekļuvi sinhronizācijas objektam un satur metodes, ko izmanto šādu objektu nodrošināšanai un atbrīvošanai. Klase CSingleLock kontrolē piekļuvi vienam sinhronizācijas objektam un klasei CMultiLock- uz vairākiem objektiem. Tālāk mēs apskatīsim tikai klasi CSingleLock.

Kad ir izveidots jebkurš sinhronizācijas objekts, piekļuvi tam var kontrolēt, izmantojot klasi CSingleLock. Lai to izdarītu, vispirms ir jāizveido tipa objekts CSingleLock izmantojot konstruktoru:

CSingleLock(CSyncObject* pObject, BOOL bInitialLock = FALSE);

Pirmais parametrs nodod rādītāju uz sinhronizācijas objektu, piemēram, semaforu. Otrā parametra vērtība nosaka, vai konstruktoram ir jāmēģina piekļūt šim objektam. Ja šis parametrs nav nulle, tad piekļuve tiks iegūta, pretējā gadījumā netiks mēģināts piekļūt. Ja piekļuve ir piešķirta, tad pavediens, kas izveidoja klases objektu CSingleLock, tiks apturēts, līdz ar metodi tiks atbrīvots atbilstošais sinhronizācijas objekts Atbloķēt klasē CSingleLock.

Kad tiek izveidots CSingleLock tipa objekts, piekļuvi objektam, uz kuru norāda pObject, var kontrolēt, izmantojot divas funkcijas: Slēdzene Un Atbloķēt klasē CSingleLock.

Metode Slēdzene ir paredzēts, lai piekļūtu objektam ar sinhronizācijas objektu. Pavediens, kas to izsauca, tiek apturēts, līdz metode tiek pabeigta, tas ir, līdz tiek iegūta piekļuve resursam. Parametra vērtība nosaka, cik ilgi funkcija gaidīs piekļuvi vajadzīgajam objektam. Katru reizi, kad metode tiek veiksmīgi pabeigta, ar sinhronizācijas objektu saistītā skaitītāja vērtība tiek samazināta par vienu.

Metode Atbloķēt Atbrīvo sinhronizācijas objektu, ļaujot citiem pavedieniem izmantot resursu. Pirmajā metodes versijā ar šo objektu saistītā skaitītāja vērtība tiek palielināta par vienu. Otrajā variantā pirmais parametrs nosaka, cik daudz šī vērtība ir jāpalielina. Otrais parametrs norāda uz mainīgo, kurā tiks ierakstīta iepriekšējā skaitītāja vērtība.

Strādājot ar klasi CSingleLock Vispārējā procedūra piekļuves resursam kontrolei ir šāda:

    izveidot CSyncObj tipa objektu (piemēram, semaforu), kas tiks izmantots, lai kontrolētu piekļuvi resursam;

    izmantojot izveidoto sinhronizācijas objektu, izveidot CSingleLock tipa objektu;

    lai piekļūtu resursam, izsauc Lock metodi;

    piekļūt resursam;

    Izsauciet atbloķēšanas metodi, lai atbrīvotu resursu.

Tālāk ir aprakstīts, kā izveidot un izmantot semaforus un notikumu objektus. Kad esat sapratis šos jēdzienus, varat viegli iemācīties un izmantot divus citus sinhronizācijas objektu veidus: kritiskās sadaļas un mutexes.

Sveiki! Šodien mēs turpināsim apsvērt vairāku vītņu programmēšanas iespējas un runāt par pavedienu sinhronizāciju.

Kas ir “sinhronizācija”? Ārpus programmēšanas jomas tas attiecas uz sava veida iestatīšanu, kas ļauj divām ierīcēm vai programmām strādāt kopā. Piemēram, viedtālruni un datoru var sinhronizēt ar Google kontu, un personīgo kontu vietnē var sinhronizēt ar kontiem sociālajos tīklos, lai, izmantojot tos, pieteiktos. Pavedienu sinhronizācijai ir līdzīga nozīme: tā ir pavedienu savstarpējās mijiedarbības iestatīšana. Iepriekšējās lekcijās mūsu pavedieni dzīvoja un strādāja atsevišķi viens no otra. Viens kaut ko skaitīja, otrs gulēja, trešais kaut ko rādīja uz pults, bet viņi savā starpā nesadarbojās. Reālās programmās šādas situācijas ir reti. Vairāki pavedieni var aktīvi darboties, piemēram, ar vienu un to pašu datu kopu un tajā kaut ko mainīt. Tas rada problēmas. Iedomājieties, ka vairāki pavedieni raksta tekstu vienā un tajā pašā vietā, piemēram, teksta failā vai konsolē. Šajā gadījumā šis fails vai konsole kļūst par koplietotu resursu. Pavedieni nezina viens par otra esamību, tāpēc viņi vienkārši pieraksta visu, ko spēj pārvaldīt laikā, ko pavedienu plānotājs tiem atvēl. Nesenajā kursa lekcijā mums bija piemērs, pie kā tas novedīs, atcerēsimies: Iemesls ir fakts, ka pavedieni strādāja ar kopīgu resursu, konsoli, nesaskaņojot darbības savā starpā. Ja pavedienu plānotājs ir atvēlējis laiku Thread-1, tas nekavējoties ieraksta visu konsolē. Nav svarīgi, ko citi pavedieni jau ir paspējuši uzrakstīt vai nav paspējuši uzrakstīt. Rezultāts, kā redzat, ir postošs. Tāpēc daudzpavedienu programmēšanā tika ieviesta īpaša koncepcija mutex (no angļu valodas “mutex”, “savstarpēja izslēgšana” - “savstarpēja izslēgšana”). Mutex uzdevums- nodrošināt mehānismu, lai noteiktā laikā objektam būtu pieeja tikai vienam pavedienam. Ja pavediens-1 ir ieguvis objekta A mutex, citiem pavedieniem tam nebūs piekļuves, lai tajā kaut ko mainītu. Kamēr objekta A mutex tiek atbrīvots, atlikušie pavedieni būs spiesti gaidīt. Reālās dzīves piemērs: iedomājieties, ka jūs un vēl 10 svešinieki piedalāties apmācībā. Vajag pēc kārtas izteikt idejas un kaut ko apspriest. Bet, tā kā jūs redzat viens otru pirmo reizi, lai nepārtraukti nepārtrauktu viens otru un neslīdētu burbulī, jūs izmantojat "runājošās bumbas" noteikumu: runāt var tikai viens cilvēks - tas, kuram ir bumba. viņa rokas. Tādā veidā diskusija izrādās adekvāta un auglīga. Tātad mutex būtībā ir tāda bumba. Ja objekta mutex atrodas viena pavediena rokās, citi pavedieni nevarēs piekļūt objektam. Lai izveidotu mutex, jums nekas nav jādara: tas jau ir iebūvēts Object klasē, kas nozīmē, ka katram Java objektam tāds ir.

Kā darbojas sinhronizētais operators

Iepazīsimies ar jaunu atslēgvārdu - sinhronizēts. Tas iezīmē noteiktu mūsu koda daļu. Ja koda bloks ir atzīmēts ar sinhronizēto atslēgvārdu, tas nozīmē, ka bloku vienlaikus var izpildīt tikai viens pavediens. Sinhronizāciju var īstenot dažādos veidos. Piemēram, izveidojiet visu sinhronizēto metodi: public synchronized void doSomething() ( //...metodes loģika) Vai arī uzrakstiet koda bloku, kur sinhronizācija tiek veikta uz kāda objekta: public class Main ( privātais Objekts obj = new Object () ; public void doSomething () ( synchronized (obj) ( ) ) ) Nozīme ir vienkārša. Ja viens pavediens ievada koda bloku, kas ir atzīmēts ar vārdu sinhronizēts, tas uzreiz iegūst objekta mutex, un visi pārējie pavedieni, kas mēģina ievadīt to pašu bloku vai metodi, ir spiesti gaidīt, līdz iepriekšējais pavediens pabeigs savu darbu un atbrīvos uzraudzīt. Starp citu! Kursa lekcijās jūs jau redzējāt sinhronizētu piemērus, taču tie izskatījās savādāk: public Void swap () ( synchronized (this) ( //...metodes loģika) ) Tēma tev ir jauna, un, protams, sākumā būs neskaidrības ar sintaksi. Tāpēc atcerieties uzreiz, lai vēlāk neapjuktu rakstīšanas metodēs. Šie divi rakstīšanas veidi nozīmē vienu un to pašu: publisks spēkā esamības maiņas () ( sinhronizēts (tas) ( //...metodes loģika) ) public synchronized void swap () ( ) ) Pirmajā gadījumā jūs izveidojat sinhronizētu koda bloku uzreiz pēc metodes ievadīšanas. Tas tiek sinhronizēts ar šo objektu, tas ir, ar pašreizējo objektu. Un otrajā piemērā jūs ievietojāt vārdu sinhronizēts visai metodei. Vairs nav skaidri jānorāda neviens objekts, uz kura tiek veikta sinhronizācija. Kad visa metode ir atzīmēta ar vārdu, šī metode tiks automātiski sinhronizēta visiem klases objektiem. Neiedziļināsimies diskusijā par to, kura metode ir labāka. Pagaidām izvēlies to, kas tev patīk vislabāk :) Galvenais ir atcerēties: sinhronizētu metodi vari pasludināt tikai tad, kad visu tajā esošo loģiku izpilda viens pavediens vienlaicīgi. Piemēram, šajā gadījumā būtu kļūda sinhronizēt doSomething() metodi: public class Main ( privātais objekts obj = new Object () ; public void doSomething () ( //...kaut kāda loģika pieejama visiem pavedieniem sinhronizēts (obj) ( //loģika, kas vienlaikus ir pieejama tikai vienam pavedienam) ) ) Kā redzat, daļa no metodes satur loģiku, kurai sinhronizācija nav nepieciešama. Tajā esošo kodu var izpildīt vairāki pavedieni vienlaikus, un visas kritiskās vietas tiek piešķirtas atsevišķam sinhronizētam blokam. Un vienu brīdi. Paskatīsimies mikroskopā uz mūsu vārdu mijmaiņas piemēru no lekcijas: public Void swap () ( sinhronizēts (tas) ( //...metodes loģika } } Pievērs uzmanību: sinhronizācija tiek veikta, izmantojot šo . Tas ir, uz konkrētu MyClass objektu. Iedomājieties, ka mums ir 2 pavedieni (Thread-1 un Thread-2) un tikai viens objekts MyClass myClass . Šajā gadījumā, ja Thread-1 izsauc metodi myClass.swap(), objekta mutex būs aizņemts, un Thread-2, mēģinot izsaukt myClass.swap(), uzkaras, gaidot, kamēr mutex atbrīvosies. Ja mums ir 2 pavedieni un 2 MyClass objekti - myClass1 un myClass2 - dažādos objektos, mūsu pavedieni var viegli vienlaikus izpildīt sinhronizētas metodes. Pirmais pavediens tiek izpildīts: myClass1. mijmaiņa (); Otrais ir: myClass2. mijmaiņa (); Šajā gadījumā sinhronizētais atslēgvārds swap() metodē neietekmēs programmas darbību, jo sinhronizācija tiek veikta konkrētam objektam. Un pēdējā gadījumā mums ir 2 objekti.Tādēļ pavedieni viens otram problēmas nerada. Galu galā diviem objektiem ir 2 dažādi mutexi, un to iegūšana nav viena no otras neatkarīga.

Sinhronizācijas iezīmes statiskās metodēs

Ko darīt, ja nepieciešams sinhronizēt statiskā metode? class MyClass ( privāta statiska String name1 = "Olya" ; privāta statiska String name2 = " Lena" ; publiska statiska sinhronizēta tukšuma maiņa () ( String s = name1; name1 = name2; name2 = s; ) ) Nav skaidrs, kas tiks šajā gadījumā pildīt mutex lomu. Galu galā mēs jau esam nolēmuši, ka katram objektam ir mutex. Bet problēma ir tā, ka, lai izsauktu statisko metodi MyClass.swap(), mums nav nepieciešami objekti: metode ir statiska! Tātad, kas būs tālāk? :/ Patiesībā ar šo problēmu nav. Java veidotāji par visu parūpējās :) Ja metode, kas satur kritisko “multithreaded” loģiku ir statiska, sinhronizācija tiks veikta pa klasēm. Lielākas skaidrības labad iepriekš minēto kodu var pārrakstīt šādi: klase MyClass ( privātā statiskā virknes nosaukums1 = "Olya" ; privāta statiskā virknes nosaukums2 = "Lena" ; publiska statiskā tukšuma apmaiņa () ( sinhronizēta (MyClass. klase ) ( String s = vārds1 ; vārds1 = vārds2; vārds2 = s; ) ) ) Principā jūs to varējāt izdomāt pats: tā kā objektu nav, tad sinhronizācijas mehānisms ir kaut kā "jāieslēdz" pašās klasēs. Tā tas ir: varat arī sinhronizēt nodarbības.