Trådsynkroniseringsverktøy i Windows OS (kritiske seksjoner, mutexes, semaforer, hendelser). Synkroniseringsobjekter i Windows Synkronisering av prosesser ved hjelp av hendelser

Forelesning nr. 9. Synkronisering av prosesser og tråder

1. Mål og midler for synkronisering.

2. Synkroniseringsmekanismer.

1. Mål og metoder for synkronisering

Det er en ganske bred klasse av operativsystemverktøy som sikrer gjensidig synkronisering av prosesser og tråder. Behovet for trådsynkronisering oppstår kun i et flerprogramoperativsystem og er forbundet med felles bruk av maskinvare og informasjonsressurser til datasystemet. Synkronisering er nødvendig for å unngå løp og vranglås ved utveksling av data mellom tråder, deling av data og tilgang til prosessoren og I/O-enheter.

I mange operativsystemer kalles disse verktøyene interprosesskommunikasjonsverktøy (IPC), som gjenspeiler den historiske forrangen til konseptet "prosess" i forhold til konseptet "tråd". Vanligvis inkluderer IPC-verktøy ikke bare synkroniseringsverktøy mellom prosesser, men også datautvekslingsverktøy mellom prosesser.

Utførelsen av en tråd i et multiprogrammeringsmiljø er alltid asynkron. Det er svært vanskelig å si med full sikkerhet hvilket stadium av utførelse en prosess vil være på på et bestemt tidspunkt. Selv i enkeltprogrammodus er det ikke alltid mulig å nøyaktig anslå tiden det tar å fullføre en oppgave. Denne tiden avhenger i mange tilfeller betydelig av verdien av kildedataene, som påvirker antall sykluser, retningen på programforgrening, utførelsestiden for I/O-operasjoner osv. Siden kildedataene på forskjellige tidspunkter når oppgaven er lansert kan være forskjellig, så kan utførelsestiden enkelte stadier og oppgaven som helhet er en svært usikker verdi.


Enda mer usikker er utførelsestiden for et program i et multiprogrammeringssystem. Øyeblikkene når tråder blir avbrutt, tiden de tilbringer i køer for delte ressurser, rekkefølgen som tråder velges ut for utførelse - alle disse hendelsene er et resultat av et sammenløp av mange omstendigheter og kan tolkes som tilfeldige. I beste fall kan man estimere de sannsynlige egenskapene til en beregningsprosess, for eksempel sannsynligheten for at den fullføres over en gitt tidsperiode.

Dermed flyter tråder i det generelle tilfellet (når programmereren ikke har tatt spesielle tiltak for å synkronisere dem) uavhengig, asynkront med hverandre. Dette gjelder både for tråder i samme prosess som kjører en felles programkode, og for tråder i forskjellige prosesser, som kjører hvert sitt program.

Enhver interaksjon mellom prosesser eller tråder er relatert til deres synkronisering, som består i å koordinere deres hastigheter ved å suspendere strømmen inntil en viss hendelse inntreffer og deretter aktivere den når denne hendelsen inntreffer. Synkronisering er kjernen i enhver trådinteraksjon, enten det involverer ressursdeling eller datautveksling. For eksempel bør mottakstråden bare få tilgang til data etter at den har blitt bufret av avsendertråden. Hvis mottakstråden fikk tilgang til dataene før den gikk inn i bufferen, må den suspenderes.

Når du deler maskinvareressurser, er synkronisering også helt nødvendig. Når for eksempel en aktiv tråd trenger tilgang til en seriell port, og en annen tråd som for øyeblikket er i ventetilstand jobber med denne porten i eksklusiv modus, suspenderer OS den aktive tråden og aktiverer den ikke før porten den trenger er gratis . Synkronisering med hendelser utenfor datasystemet, for eksempel reaksjonen på å trykke på tastekombinasjonen Ctrl+C, er ofte også nødvendig.

Hvert sekund oppstår hundrevis av hendelser i systemet relatert til allokering og frigjøring av ressurser, og operativsystemet må ha pålitelige og effektive midler som gjør at det kan synkronisere tråder med hendelser som skjer i systemet.

For å synkronisere applikasjonsprogramtråder kan programmereren bruke både sine egne synkroniseringsverktøy og -teknikker, og operativsystemverktøy. For eksempel kan to tråder i samme applikasjonsprosess koordinere arbeidet sitt ved å bruke en global boolsk variabel som er tilgjengelig for dem begge, som er satt til én når en hendelse inntreffer, for eksempel produserer den ene tråden dataene som er nødvendige for at den andre skal fortsette å jobbe. Men i mange tilfeller er synkroniseringsfasilitetene som tilbys av operativsystemet i form av systemanrop mer effektive, eller til og med de eneste mulige. Dermed kan ikke tråder som tilhører forskjellige prosesser på noen måte forstyrre hverandres arbeid. Uten formidling av operativsystemet kan de ikke suspendere hverandre eller varsle hverandre om forekomsten av en hendelse. Synkroniseringsverktøy brukes av operativsystemet, ikke bare for å synkronisere applikasjonsprosesser, men også for dets interne behov.

Operativsystemutviklere tilbyr vanligvis et bredt spekter av synkroniseringsverktøy til disposisjon for applikasjons- og systemprogrammerere. Disse verktøyene kan danne et hierarki, når mer komplekse bygges på grunnlag av enklere verktøy, og også være funksjonelt spesialiserte, for eksempel verktøy for å synkronisere tråder i én prosess, verktøy for å synkronisere tråder av ulike prosesser ved utveksling av data, etc. Ofte overlapper funksjonaliteten til forskjellige systemanropssynkroniseringer, slik at en programmerer kan bruke flere samtaler for å løse ett problem, avhengig av hans personlige preferanser.


Behovet for synkronisering og rase

Å neglisjere synkroniseringsproblemer i et flertråds system kan føre til feil løsning av problemet eller til og med systemkrasj. Tenk for eksempel (fig. 4.16), oppgaven med å vedlikeholde en database over klienter til en bestemt bedrift. Hver klient får tildelt en egen post i databasen, som blant annet inneholder feltene Ordre og Betaling. Programmet som vedlikeholder databasen er utformet som en enkelt prosess som har flere tråder, inkludert tråd A, som legger inn informasjon om bestillinger mottatt fra kunder i databasen, og tråd B som registrerer i databasen informasjon om kundebetalinger for fakturaer. Begge disse trådene fungerer sammen på en felles databasefil ved å bruke de samme algoritmene, som inkluderer tre trinn.

2. Skriv inn en ny verdi i feltet Ordre (for flyt A) eller Betaling (for flyt B).

3. Returner den endrede posten til databasefilen.

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

Ris. 4.17. Påvirkningen av relative strømningshastigheter på resultatet av å løse problemet

Kritisk seksjon

Et viktig konsept i trådsynkronisering er konseptet med en "kritisk del" av et program. Kritisk seksjon er en del av et program hvis kjøringsresultat kan endres uforutsigbart hvis variabler relatert til den delen av programmet endres av andre tråder mens kjøringen av den delen ennå ikke er fullført. Den kritiske delen er alltid definert i forhold til visse kritiske data hvis endret på en ukoordinert måte, kan det oppstå uønskede effekter. I det forrige eksemplet var de kritiske dataene databasefilpostene. Alle tråder som arbeider med kritiske data må ha en kritisk del definert. Merk at i forskjellige tråder består den kritiske delen generelt av forskjellige sekvenser av kommandoer.

For å eliminere effekten av løp på kritiske data, er det nødvendig å sikre at kun én tråd er i den kritiske delen knyttet til disse dataene til enhver tid. Det spiller ingen rolle om denne tråden er i aktiv eller suspendert tilstand. Denne teknikken kalles gjensidig utelukkelse. Operativsystemet bruker forskjellige måter å implementere gjensidig ekskludering på. Noen metoder er egnet for gjensidig ekskludering når bare tråder fra én prosess kommer inn i den kritiske delen, mens andre kan gi gjensidig ekskludering for tråder av ulike prosesser.

Den enkleste og samtidig mest ineffektive måten å sikre gjensidig ekskludering på er at operativsystemet lar tråden deaktivere eventuelle avbrudd mens den er i den kritiske delen. Denne metoden brukes imidlertid praktisk talt ikke, siden det er farlig å stole på en brukertråd for å kontrollere systemet - den kan okkupere prosessoren i lang tid, og hvis en tråd krasjer i en kritisk seksjon, vil hele systemet krasje, fordi avbrudd vil aldri bli tillatt.

2. Synkroniseringsmekanismer.

Blokkering av variabler

For å synkronisere tråder i én prosess, kan en applikasjonsprogrammerer bruke global blokkerende variabler. Programmereren jobber med disse variablene, som alle tråder i prosessen har direkte tilgang til, uten å ty til OS-systemanrop.

Noe som ville deaktivere avbrudd gjennom hele verifiserings- og installasjonsoperasjonen.

Implementering av gjensidig ekskludering på måten beskrevet ovenfor har en betydelig ulempe: i løpet av tiden som en tråd er i den kritiske delen, vil en annen tråd som trenger den samme ressursen, som har tilgang til prosessoren, kontinuerlig spørre blokkeringsvariabelen, og kaste bort prosessortiden tildelt den, som kan brukes til å kjøre en annen tråd. For å eliminere denne ulempen gir mange operativsystemer spesielle systemoppfordringer for å jobbe med kritiske seksjoner.

I fig. Figur 4.19 viser hvordan disse funksjonene implementerer gjensidig ekskludering i Windows NT-operativsystemet. Før du begynner å endre kritiske data, sender tråden ut EnterCriticalSection()-systemkallet. Dette kallet utfører først, som i det forrige tilfellet, en sjekk av blokkeringsvariabelen som gjenspeiler tilstanden til den kritiske ressursen. Hvis systemkallet fastslår at ressursen er opptatt (F(D) = 0), utfører den i motsetning til det forrige tilfellet ikke en syklisk undersøkelse, men setter tråden i ventetilstand (D) og noterer at denne tråden skal aktiveres når den tilsvarende ressursen blir tilgjengelig. Tråden som for øyeblikket bruker denne ressursen, etter å ha avsluttet den kritiske delen, må utføre LeaveCriticalSectionO-systemfunksjonen, som et resultat av at blokkeringsvariabelen tar verdien som tilsvarer den frie tilstanden til ressursen (F(D) = 1), og operativsystemet ser gjennom køen til de som venter på denne ressurstråden og flytter den første tråden fra køen til klar-tilstanden.

Overheadkostnader" href="/text/category/nakladnie_rashodi/" rel="bookmark">OS-overheadkostnader for implementering av funksjonen for å gå inn og ut av den kritiske delen kan overstige de oppnådde besparelsene.

Semaforer

En generalisering av blokkerende variabler er de såkalte Dijkstra semaforer. I stedet for binære variabler foreslo Dijkstra å bruke variabler som kan ta ikke-negative heltallsverdier. Slike variabler, som brukes til å synkronisere databehandlingsprosesser, kalles semaforer.

For å jobbe med semaforer introduseres to primitiver, tradisjonelt betegnet P og V. La variabelen S representere en semafor. Deretter er handlingene V(S) og P(S) definert som følger.

* V(S): Variabelen S økes med 1 som en enkelt handling. Prøvetaking, bygging og lagring kan ikke avbrytes. Variabel S får ikke tilgang til andre tråder mens denne operasjonen utføres.

* P(S): Reduserer S med 1 hvis mulig. Hvis 5=0 og det er umulig å redusere S mens du forblir i området med ikke-negative heltallsverdier, venter trådkallingsoperasjonen P til denne reduksjonen blir mulig. En vellykket kontroll og nedgang er også en udelelig operasjon.

Ingen avbrudd er tillatt under utførelsen av V- og P-primitivene.

I det spesielle tilfellet hvor semaforen S bare kan ta verdiene 0 og 1, blir den en blokkerende variabel, som av denne grunn ofte kalles en binær semafor. Aktivitet P har potensial til å sette tråden som kjører den i en ventetilstand, mens aktivitet V under noen omstendigheter kan vekke en annen tråd som ble suspendert av aktivitet P.

La oss se på bruken av semaforer ved å bruke et klassisk eksempel på samspillet mellom to tråder som kjører i multiprogrammeringsmodus, hvorav den ene skriver data til bufferbassenget, og den andre leser den fra bufferbassenget. La bufferpoolen bestå av N buffere, som hver kan inneholde én oppføring. Generelt kan forfattertråden og lesertråden ha forskjellige hastigheter og få tilgang til bufferbassenget med varierende intensitet. I en periode kan skrivehastigheten overstige lesehastigheten, i en annen - omvendt. For å fungere sammen på riktig måte, må forfattertråden settes på pause når alle buffere er opptatt og våkne når minst én buffer er frigjort. Derimot bør lesertråden pause når alle buffere er tomme og våkne opp når minst én skriving vises.

La oss introdusere to semaforer: e - antall tomme buffere, og f - antall fylte buffere, og i starttilstanden e = N, a f = 0. Da kan driften av tråder med en felles bufferpool beskrives som følger (Fig. 4.20).

Writer-tråden utfører først en P(e)-operasjon, der den sjekker om det er noen tomme buffere i bufferpoolen. I samsvar med semantikken til P-operasjonen, hvis semaforen e er lik 0 (det vil si at det ikke er noen ledige buffere for øyeblikket), går skrivertråden inn i ventetilstanden. Hvis verdien av e er et positivt tall, reduserer den antall ledige buffere, skriver data til neste ledige buffer, og øker deretter antallet okkuperte buffere med operasjonen V(f). Lesertråden fungerer på en lignende måte, med den forskjellen at den starter med å se etter fulle buffere, og etter å ha lest dataene øker den antall ledige buffere.

DIV_ADBLOCK860">

En semafor kan også brukes som en blokkerende variabel. I eksemplet diskutert ovenfor, for å eliminere kollisjoner når du arbeider med et delt minneområde, vil vi anta at skriving til og lesing fra bufferen er kritiske seksjoner. Vi vil sikre gjensidig ekskludering ved å bruke den binære semaforen b (fig. 4.21). Begge trådene, etter å ha sjekket tilgjengeligheten av buffere, må sjekke tilgjengeligheten til den kritiske delen.

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

Ris. 4.22. Forekomsten av vranglås under programkjøring

MERK

Deadlocks må skilles fra enkle køer, selv om begge oppstår når ressurser deles og ser like ut: en tråd er suspendert og venter på at en ressurs skal bli fri. En kø er imidlertid et normalt fenomen, et iboende tegn på høy ressursutnyttelse når forespørsler kommer tilfeldig. En kø vises når en ressurs ikke er tilgjengelig for øyeblikket, men vil bli frigitt etter en stund, slik at tråden kan fortsette å kjøre. En dødlås, som navnet antyder, er en noe uløselig situasjon. En nødvendig forutsetning for at en vranglås skal oppstå er at en tråd trenger flere ressurser samtidig.

I de vurderte eksemplene ble dødlåsen dannet av to tråder, men flere tråder kan gjensidig blokkere hverandre. I fig. Figur 2.23 viser en slik fordeling av ressursene Ri mellom flere tråder Tj, som førte til at det oppsto vranglås. Pilene angir flytens ressursbehov. En hel pil betyr at den tilsvarende ressursen er allokert til tråden, og en stiplet pil kobler tråden til ressursen som er nødvendig, men som ennå ikke kan tildeles fordi den er okkupert av en annen tråd. For eksempel trenger tråd T1 ressursene R1 og R2 for å utføre arbeid, hvorav bare én er tildelt - R1, og ressurs R2 holdes av tråden T2. Ingen av de fire trådene vist i figuren kan fortsette arbeidet, siden de ikke har alle ressursene som er nødvendige for dette.

Tråders manglende evne til å fullføre arbeidet de har startet på grunn av vranglås reduserer ytelsen til datasystemet. Derfor vies mye oppmerksomhet til problemet med å forhindre vranglås. I tilfelle en vreklås oppstår, må systemet gi operatøroperatøren et middel som gjør at han kan gjenkjenne en vreklås og skille den fra en normal blokkering på grunn av midlertidig utilgjengelighet av ressurser. Til slutt, hvis en vranglås blir diagnostisert, er det nødvendig med midler for å fjerne vranglåser og gjenopprette den normale dataprosessen.

Eieren" href="/text/category/vladeletc/" rel="bookmark">eieren, setter den til en usignalert tilstand og går inn i den kritiske delen. Etter at tråden har fullført arbeidet med de kritiske dataene, "gir den opp" mutexen, setter den inn i den signalerte tilstanden. For øyeblikket er mutexen fri og tilhører ikke noen tråd. Hvis en tråd venter på at den skal slippes, blir den neste eier av denne mutexen, kl. samtidig går mutexen inn i ikke-signalert tilstand.

Et hendelsesobjekt (i dette tilfellet brukes ordet "hendelse" i en snever betydning, som en betegnelse på en bestemt type synkroniseringsobjekt) brukes vanligvis ikke for å få tilgang til data, men for å varsle andre tråder om at noen handlinger har fullført. La, for eksempel, i noen applikasjoner, arbeid organiseres på en slik måte at en tråd leser data fra en fil inn i en minnebuffer, og andre tråder behandler disse dataene, deretter leser den første tråden en ny del av data, og andre tråder behandle det igjen, og så videre. Ved starten av kjøringen setter den første tråden hendelsesobjektet til en ikke-signalert tilstand. Alle andre tråder har gjort et kall til Wait(X), der X er en hendelsespeker, og er i suspendert tilstand og venter på at hendelsen skal inntreffe. Så snart bufferen er full, rapporterer den første tråden dette til operativsystemet ved å kalle Set(X). Operativsystemet skanner køen av ventende tråder og aktiverer alle tråder som venter på denne hendelsen.

Signaler

Signal Lar en oppgave svare på en hendelse, hvis kilde kan være operativsystemet eller en annen oppgave. Signaler inkluderer å avbryte en oppgave og utføre forhåndsbestemte handlinger. Signaler kan genereres synkront, det vil si som et resultat av arbeidet med selve prosessen, eller de kan sendes til en prosess av en annen prosess, det vil si generert asynkront. Synkrone signaler kommer oftest fra prosessorens avbruddssystem og indikerer prosesshandlinger som er blokkert av maskinvaren, for eksempel deling med null, adresseringsfeil, brudd på minnebeskyttelsen, etc.

Et eksempel på et asynkront signal er et signal fra en terminal. Mange operativsystemer sørger for umiddelbar fjerning av en prosess fra utførelse. For å gjøre dette kan brukeren trykke på en bestemt tastekombinasjon (Ctrl+C, Ctrl+Break), som et resultat av at OS genererer et signal og sender det til den aktive prosessen. Signalet kan ankomme når som helst under en prosess utførelse (det vil si at det er asynkront), noe som krever at prosessen avsluttes umiddelbart. I dette tilfellet er svaret på signalet den ubetingede fullføringen av prosessen.

Et sett med signaler kan defineres i systemet. Programkoden til prosessen som mottok signalet kan enten ignorere det, eller svare på det med en standard handling (for eksempel exit), eller utføre spesifikke handlinger definert av applikasjonsprogrammereren. I sistnevnte tilfelle er det nødvendig å gi spesielle systemanrop i programkoden, ved hjelp av hvilke operativsystemet blir informert om hvilken prosedyre som skal utføres som svar på mottak av et bestemt signal.

Signaler gir logisk kommunikasjon mellom prosesser og mellom prosesser og brukere (terminaler). Siden sending av et signal krever kunnskap om prosessidentifikatoren, er interaksjon via signaler kun mulig mellom relaterte prosesser som kan innhente informasjon om hverandres identifikatorer.

I distribuerte systemer som består av flere prosessorer, som hver har sin egen RAM, er låsevariabler, semaforer, signaler og andre lignende delt minnebaserte funksjoner uegnet. I slike systemer kan synkronisering bare oppnås gjennom meldingsutveksling.

En prosess er en forekomst av et program som er lastet inn i minnet. Denne forekomsten kan lage tråder, som er en sekvens av instruksjoner som skal utføres. Det er viktig å forstå at det ikke er prosesser som kjører, men snarere tråder.

Dessuten har enhver prosess minst én tråd. Denne tråden kalles applikasjonens hovedtråd (hovedtråd).

Siden det nesten alltid er mange flere tråder enn det er fysiske prosessorer for å kjøre dem, kjøres ikke trådene faktisk samtidig, men i sin tur (prosessortiden fordeles mellom trådene). Men å bytte mellom dem skjer så ofte at de ser ut til å kjøre parallelt.

Avhengig av situasjonen kan tråder være i tre tilstander. For det første kan en tråd kjøres når den er tildelt CPU-tid, dvs. det kan være i aktivitet. For det andre kan den være inaktiv og vente på at prosessoren skal tildeles, dvs. være i beredskap. Og det er en tredje, også veldig viktig stat - blokkeringsstaten. Når en tråd er blokkert, blir den ikke tildelt noe tidspunkt i det hele tatt. Vanligvis plasseres en blokk mens du venter på en hendelse. Når denne hendelsen inntreffer, flyttes tråden automatisk fra blokkert tilstand til klar tilstand. For eksempel, hvis en tråd utfører beregninger, og den andre må vente på resultatene for å lagre dem på disk. Den andre kan bruke en sløyfe som "while(!isCalcFinished) continue;", men det er lett å verifisere i praksis at under utførelsen av denne løkken er prosessoren 100 % opptatt (dette kalles aktiv venting). Slike sykluser bør om mulig unngås, der låsemekanismen gir uvurderlig hjelp. Den andre tråden kan blokkere seg selv til den første tråden reiser en hendelse som indikerer at lesingen er fullført.

Synkronisering av tråder i Windows OS

Windows implementerer forebyggende multitasking - dette betyr at systemet når som helst kan avbryte kjøringen av en tråd og overføre kontroll til en annen. Tidligere, i Windows 3.1, ble en organisasjonsmetode kalt cooperativ multitasking brukt: systemet ventet til tråden selv overførte kontrollen til den, og det er derfor, hvis en applikasjon frøs, måtte datamaskinen startes på nytt.

Alle tråder som tilhører samme prosess deler noen felles ressurser - for eksempel RAM-adresserom eller åpne filer. Disse ressursene tilhører hele prosessen, og derfor til hver av dens tråder. Derfor kan hver tråd arbeide med disse ressursene uten noen begrensninger. Men... Hvis en tråd ennå ikke er ferdig med å jobbe med en delt ressurs, og systemet bytter til en annen tråd som bruker den samme ressursen, kan resultatet av arbeidet med disse trådene være ekstremt forskjellig fra det som var ment. Slike konflikter kan også oppstå mellom tråder som tilhører ulike prosesser. Når to eller flere tråder deler en delt ressurs, oppstår dette problemet.

Eksempel. Tråder som ikke er synkroniserte: Hvis du midlertidig stopper visningstråden (pause), vil bakgrunnstråden fortsette å kjøre.

#inkludere #inkludere int a; HÅNDTAK hThr; usignert lang uThrID; void Thread(void* pParams) ( int i, num = 0; mens (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; }

Dette er grunnen til at en mekanisme er nødvendig for å tillate tråder å koordinere arbeidet med delte ressurser. Denne mekanismen kalles trådsynkroniseringsmekanismen.

Denne mekanismen er et sett med operativsystemobjekter som opprettes og administreres programmatisk, er felles for alle tråder i systemet (noen deles av tråder som tilhører samme prosess), og brukes til å koordinere tilgang til ressurser. Ressurser kan være alt som kan deles av to eller flere tråder - en diskfil, en port, en databaseoppføring, et GDI-objekt og til og med en global programvariabel (som kan nås av tråder som tilhører samme prosess).

Det er flere synkroniseringsobjekter, hvorav de viktigste er mutexen, den kritiske delen, hendelsen og semaforen. Hvert av disse objektene implementerer sin egen synkroniseringsmetode. Dessuten kan selve prosesser og tråder brukes som synkroniseringsobjekter (når en tråd venter på at en annen tråd eller prosess skal fullføres); samt filer, kommunikasjonsenheter, konsollinndata og endringsvarsler.

Ethvert synkroniseringsobjekt kan være i såkalt signaltilstand. For hver type objekt har denne tilstanden en annen betydning. Tråder kan sjekke gjeldende tilstand til et objekt og/eller vente på en endring i denne tilstanden og dermed koordinere handlingene deres. Dette sikrer at når en tråd arbeider med synkroniseringsobjekter (oppretter dem, endrer tilstand), vil ikke systemet avbryte kjøringen før det fullfører denne handlingen. Dermed er alle sluttoperasjoner med synkroniseringsobjekter atomære (udelelige.

Arbeide med synkroniseringsobjekter

For å lage et eller annet synkroniseringsobjekt kalles en spesiell WinAPI-funksjon av typen Create... (for eksempel CreateMutex). Dette kallet returnerer et håndtak til et objekt (HANDLE) som kan brukes av alle tråder som tilhører denne prosessen. Det er mulig å få tilgang til et synkroniseringsobjekt fra en annen prosess – enten ved å arve et håndtak til dette objektet, eller helst ved å bruke et kall til objektets åpningsfunksjon (Åpne...). Etter denne samtalen vil prosessen motta et håndtak som senere kan brukes til å arbeide med objektet. Et objekt, med mindre det er ment å brukes i en enkelt prosess, må gis et navn. Navnene på alle objekter må være forskjellige (selv om de er av forskjellige typer). Du kan for eksempel ikke opprette en hendelse og en semafor med samme navn.

Ved å bruke den eksisterende beskrivelsen av et objekt kan du bestemme dets nåværende tilstand. Dette gjøres ved hjelp av såkalte. ventende funksjoner. Den mest brukte funksjonen er WaitForSingleObject. Denne funksjonen tar to parametere, hvorav den første er objekthåndtaket, den andre er tidsavbruddet i ms. Funksjonen returnerer WAIT_OBJECT_0 hvis objektet er signalisert, WAIT_TIMEOUT hvis det ble tidsavbrutt, og WAIT_ABANDONED hvis mutex-objektet ikke ble frigjort før dets egen tråd gikk ut. Hvis tidsavbruddet angis som null, returnerer funksjonen resultatet umiddelbart, ellers venter den i den angitte tiden. Hvis objektets tilstand blir signal før denne tiden utløper, vil funksjonen returnere WAIT_OBJECT_0, ellers vil funksjonen returnere WAIT_TIMEOUT. Hvis den symbolske konstanten UENDELIG er spesifisert som tiden, vil funksjonen vente på ubestemt tid til objektets tilstand blir signal.

Et veldig viktig faktum er at oppkalling av en ventefunksjon blokkerer den aktuelle tråden, dvs. Mens en tråd er i inaktiv tilstand, blir den ikke tildelt noen CPU-tid.

Kritiske avsnitt

Et kritisk seksjonsobjekt hjelper programmereren med å isolere kodedelen der en tråd får tilgang til en delt ressurs og forhindre samtidig bruk av ressursen. Før du bruker ressursen, går tråden inn i den kritiske delen (kaller EnterCriticalSection-funksjonen). Hvis en annen tråd deretter prøver å gå inn i den samme kritiske delen, vil kjøringen av den stoppe til den første tråden forlater delen ved å kalle LeaveCriticalSection. Brukes kun for tråder i én prosess. Rekkefølgen for oppføring i den kritiske delen er ikke definert.

Det er også en TryEnterCriticalSection-funksjon som sjekker om den kritiske delen er opptatt. Med sin hjelp kan tråden, mens den venter på tilgang til en ressurs, ikke blokkeres, men utføre noen nyttige handlinger.

Eksempel. Synkronisering av tråder ved hjelp av kritiske seksjoner.

#inkludere #inkludere CRITICAL_SECTION cs; int a; HÅNDTAK hThr; usignert lang uThrID; void Thread(void* pParams) ( int i, num = 0; mens (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; }

Gjensidige utelukkelser

Gjensidig ekskluderingsobjekter (mutexes, mutex - fra MUTual EXclusion) lar deg koordinere gjensidig utelukkelse av tilgang til en delt ressurs. Signaltilstanden til et objekt (dvs. "sett"-tilstanden) tilsvarer et tidspunkt da objektet ikke tilhører noen tråd og kan "fanges". Motsatt tilsvarer tilstanden "tilbakestilling" (ikke-signal) øyeblikket når en tråd allerede eier dette objektet. Tilgang til et objekt gis når tråden som eier objektet slipper det.

To (eller flere) tråder kan lage en mutex med samme navn ved å kalle CreateMutex-funksjonen. Den første tråden lager faktisk en mutex, og de neste får et håndtak til et allerede eksisterende objekt. Dette lar flere tråder få et håndtak til samme mutex, og frigjør programmereren fra å måtte bekymre seg om hvem som faktisk oppretter mutexen. Hvis denne tilnærmingen brukes, er det tilrådelig å sette bInitialOwner-flagget til FALSE, ellers vil det være noen problemer med å bestemme den faktiske skaperen av mutexen.

Flere tråder kan få et håndtak til samme mutex, noe som tillater kommunikasjon mellom prosesser. Følgende mekanismer for denne tilnærmingen kan brukes:

  • En underordnet prosess opprettet ved hjelp av CreateProcess-funksjonen kan arve et mutex-håndtak hvis parameteren lpMutexAttributes ble spesifisert da mutexen ble opprettet med CreateMutex-funksjonen.
  • En tråd kan få et duplikat av en eksisterende mutex ved å bruke DuplicateHandle-funksjonen.
  • En tråd kan spesifisere navnet på en eksisterende mutex når du kaller opp OpenMutex- eller CreateMutex-funksjonene.

For å erklære et gjensidig unntak som tilhørende den aktuelle tråden, må du ringe en av ventefunksjonene. Tråden som eier objektet kan gjenanskaffe det så mange ganger den vil (dette vil ikke føre til selvlåsing), men den må frigjøre det like mange ganger ved å bruke ReleaseMutex-funksjonen.

For å synkronisere trådene i én prosess er det mer effektivt å bruke kritiske seksjoner.

Eksempel. Synkronisering av tråder ved hjelp av mutexes.

#inkludere #inkludere HÅNDTAK hMutex; int a; HÅNDTAK hThr; usignert lang uThrID; void Thread(void* pParams) ( int i, num = 0; mens (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; }

arrangementer

Hendelsesobjekter brukes til å varsle ventende tråder om at en hendelse har skjedd. Det er to typer hendelser - med manuell og automatisk tilbakestilling. Manuell tilbakestilling utføres av ResetEvent-funksjonen. Manuelle tilbakestillingshendelser brukes til å varsle flere tråder samtidig. Når du bruker en automatisk tilbakestillingshendelse, vil bare én ventende tråd motta varselet og fortsette å kjøre; resten vil fortsette å vente.

CreateEvent-funksjonen oppretter et hendelsesobjekt, SetEvent - setter hendelsen til signaltilstanden, ResetEvent - tilbakestiller hendelsen. PulseEvent-funksjonen setter en hendelse, og etter at trådene som venter på denne hendelsen er gjenopptatt (alle med manuell tilbakestilling og bare én med automatisk tilbakestilling), tilbakestiller den den. Hvis det ikke er noen ventende tråder, tilbakestiller PulseEvent ganske enkelt hendelsen.

Eksempel. Synkronisering av tråder ved hjelp av hendelser.

#inkludere #inkludere HANDLE hEvent1, hEvent2; int a; HÅNDTAK hThr; usignert lang uThrID; void Thread(void* pParams) ( int i, num = 0; mens (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; }

Semaforer

Et semaforobjekt er faktisk et mutex-objekt med en teller. Dette objektet lar seg "fange" av et visst antall tråder. Etter dette vil "fange" være umulig før en av trådene som tidligere "fanget" semaforen slipper den. Semaforer brukes for å begrense antall tråder som samtidig arbeider med en ressurs. Maksimalt antall tråder overføres til objektet under initialisering; etter hver "fangst" reduseres semafortelleren. Signaltilstanden tilsvarer en tellerverdi større enn null. Når telleren er null, anses semaforen som ikke installert (tilbakestilt).

CreateSemaphore-funksjonen oppretter et semaforobjekt som indikerer dets maksimalt mulige startverdi, OpenSemaphore - returnerer en deskriptor av en eksisterende semafor, semaforen fanges opp ved hjelp av ventefunksjoner, og semaforverdien reduseres med én, ReleaseSemaphore - semaforen frigjøres med semaforen verdi økt med verdien spesifisert i parameternummeret.

Eksempel. Synkronisering av tråder ved hjelp av semaforer.

#inkludere #inkludere HÅNDTAK hSem; int a; HÅNDTAK hThr; usignert lang uThrID; void Thread(void* pParams) ( int i, num = 0; mens (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; }

Beskyttet tilgang til variabler

Det finnes en rekke funksjoner som lar deg jobbe med globale variabler fra alle tråder uten å bekymre deg for synkronisering, fordi disse funksjonene overvåker det selv - utførelsen deres er atomær. Disse funksjonene er InterlockedIncrement, InterlockedDecrement, InterlockedExchange, InterlockedExchangeAdd og InterlockedCompareExchange. For eksempel øker InterlockedIncrement-funksjonen atomisk verdien av en 32-bits variabel med én, noe som er praktisk å bruke for forskjellige tellere.

For å få fullstendig informasjon om formålet, bruken og syntaksen til alle WIN32 API-funksjoner, må du bruke MS SDK-hjelpesystemet inkludert i Borland Delphi- eller CBuilder-programmeringsmiljøene, samt MSDN, som leveres som en del av Visual C-programmeringssystemet.

Tråder kan være i en av flere tilstander:

    Klar(klar) – ligger i bassenget av tråder som venter på utførelse;

    Løping(utførelse) - kjører på prosessoren;

    Venter(venter), også kalt inaktiv eller suspendert, suspendert - i en tilstand av venting, som ender med at tråden begynner å kjøre (Running state) eller går inn i tilstanden Klar;

    Avsluttet (fullføring) - utførelse av alle trådkommandoer er fullført. Den kan slettes senere. Hvis strømmen ikke slettes, kan systemet tilbakestille den til sin opprinnelige tilstand for senere bruk.

Trådsynkronisering

Løpende tråder må ofte kommunisere på en eller annen måte. For eksempel, hvis flere tråder prøver å få tilgang til noen globale data, må hver tråd beskytte dataene fra å bli endret av en annen tråd. Noen ganger må en tråd vite når en annen tråd vil fullføre en oppgave. Slik interaksjon er obligatorisk mellom tråder av både samme og forskjellige prosesser.

Trådsynkronisering ( tråd synkronisering) er et generelt begrep som refererer til prosessen med interaksjon og sammenkobling av tråder. Vær oppmerksom på at synkronisering av tråder krever at operativsystemet selv fungerer som mellomledd. Tråder kan ikke samhandle med hverandre uten hennes deltakelse.

I Win32 er det flere metoder for å synkronisere tråder. Det hender at i en bestemt situasjon er en metode mer å foretrekke enn en annen. La oss ta en rask titt på disse metodene.

Kritiske avsnitt

En metode for å synkronisere tråder er å bruke kritiske seksjoner. Dette er den eneste trådsynkroniseringsmetoden som ikke krever Windows-kjernen. (Den kritiske delen er ikke et kjerneobjekt.) Denne metoden kan imidlertid bare brukes til å synkronisere tråder i en enkelt prosess.

En kritisk seksjon er en kodedel som bare kan kjøres av én tråd om gangen. Hvis koden som brukes til å initialisere en matrise er plassert i en kritisk seksjon, vil ikke andre tråder kunne gå inn i den delen av koden før den første tråden er ferdig med å utføre den.

Før du bruker en kritisk seksjon, må du initialisere den ved å bruke Win32 API-prosedyren InitializeCriticalSection(), som er definert (i Delphi) som følger:

prosedyre InitializeCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

IpCriticalSection-parameteren er en post av typen TRTLCriticalSection som sendes ved referanse. Den nøyaktige definisjonen av TRTLCriticalSection-oppføringen spiller ingen rolle, siden det er usannsynlig at du noen gang trenger å se på innholdet. Alt du trenger å gjøre er å sende en uinitialisert oppføring til parameteren IpCtitical Section, og denne oppføringen vil umiddelbart fylles ut av prosedyren.

Etter å ha fylt ut oppføringen i programmet, kan du opprette en kritisk seksjon ved å plassere en del av teksten mellom kallene til EnterCriticalSection()- og LeaveCriticalSection()-funksjonene. Disse prosedyrene er definert som følger:

prosedyre EnterCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

prosedyre LeaveCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

IpCriticalSection-parameteren som sendes til disse prosedyrene er ikke annet enn en oppføring opprettet av InitializeCriticalSection()-prosedyren.

Funksjon EnterCriticalSection sjekker om en annen tråd allerede kjører den kritiske delen av programmet knyttet til det gitte kritiske seksjonsobjektet. Hvis ikke, får tråden tillatelse til å kjøre sin kritiske kode, eller rettere sagt, den er ikke forhindret i å gjøre det. I så fall settes tråden som sender forespørselen i en ventetilstand, og forespørselen registreres. Fordi poster må opprettes, er det kritiske seksjonsobjektet en datastruktur.

Når funksjon LeaveCriticalSection kalt opp av en tråd som for øyeblikket har tillatelse til å utføre sin kritiske seksjon med kode knyttet til et gitt kritisk seksjonsobjekt, kan systemet sjekke om det er en annen tråd i køen som venter på at objektet skal slippes. Systemet kan da fjerne den ventende tråden fra ventetilstanden, og den vil fortsette arbeidet (i tidsstykkene som er tildelt den).

Når du er ferdig med å jobbe med TRTLCriticalSection-posten, må du frigjøre den ved å ringe DeleteCriticalSection()-prosedyren, som er definert som følger:

prosedyre DeleteCriticalSection(var IpCriticalSection: TRTLCriticalSection); stdcall;

Noen ganger blir det nødvendig når du jobber med flere tråder eller prosesser synkronisere utførelse to eller flere av dem. Grunnen til dette er oftest at to eller flere tråder kan kreve tilgang til en delt ressurs som egentlig kan ikke leveres til flere tråder samtidig. En delt ressurs er en ressurs som kan nås samtidig av flere kjørende oppgaver.

Mekanismen som sikrer synkroniseringsprosessen kalles begrensning av tilgang. Behovet for det oppstår også i tilfeller der en tråd venter på en hendelse generert av en annen tråd. Naturligvis må det være en måte som den første tråden vil bli suspendert på før hendelsen inntreffer. Etter dette skal tråden fortsette utføringen.

Det er to generelle tilstander som en oppgave kan være i. For det første kan oppgaven bli utført(eller vær klar til å kjøre så snart den får tilgang til CPU-ressurser). For det andre kan oppgaven være blokkert. I dette tilfellet suspenderes utførelsen til ressursen den trenger er frigjort eller en bestemt hendelse inntreffer.

Windows har spesielle tjenester som lar deg begrense tilgangen til delte ressurser på en bestemt måte, fordi uten hjelp fra operativsystemet kan ikke en egen prosess eller tråd selv avgjøre om den har enetilgang til en ressurs. Windows-operativsystemet inneholder en prosedyre som under én kontinuerlig operasjon kontrollerer og om mulig setter ressurstilgangsflagget. På språket til operativsystemutviklere kalles denne operasjonen kontroll og installasjonsoperasjon. Flagg som brukes til å gi synkronisering og kontrolltilgang til ressurser kalles semaforer(semafor). Win32 API gir støtte for semaforer og andre synkroniseringsobjekter. MFC-biblioteket inkluderer også støtte for disse objektene.

Synkroniseringsobjekter og mfc-klasser

Win32-grensesnittet støtter fire typer synkroniseringsobjekter - alle er på en eller annen måte basert på konseptet en semafor.

Den første typen objekt er selve semaforen, eller klassisk (standard) semafor. Den lar et begrenset antall prosesser og tråder få tilgang til én enkelt ressurs. I dette tilfellet er tilgangen til ressursen enten fullstendig begrenset (en og bare én tråd eller prosess kan få tilgang til ressursen i en viss tidsperiode), eller bare et lite antall tråder og prosesser får samtidig tilgang. Semaforer implementeres ved hjelp av en teller hvis verdi synker når en semafor er allokert til en oppgave, og øker når oppgaven slipper en semafor.

Den andre typen synkroniseringsobjekter er eksklusiv (mutex) semafor. Den er designet for å begrense tilgangen til en ressurs fullstendig, slik at bare én prosess eller tråd kan få tilgang til ressursen til enhver tid. Faktisk er dette en spesiell type semafor.

Den tredje typen synkroniseringsobjekter er begivenhet, eller hendelsesobjekt. Den brukes til å blokkere tilgang til en ressurs inntil en annen prosess eller tråd erklærer at ressursen kan brukes. Dermed signaliserer dette objektet fullføringen av den nødvendige hendelsen.

Ved å bruke et fjerde type synkroniseringsobjekt kan du forby kjøring av visse deler av programkoden av flere tråder samtidig. For å gjøre dette må disse områdene deklareres som kritisk seksjon. Når en tråd kommer inn i denne delen, er det forbudt for andre tråder å gjøre det samme før den første tråden går ut av delen.

Kritiske seksjoner, i motsetning til andre typer synkroniseringsobjekter, brukes kun til å synkronisere tråder innenfor samme prosess. Andre typer objekter kan brukes til å synkronisere tråder i en prosess eller til å synkronisere prosesser.

I MFC støttes synkroniseringsmekanismen levert av Win32-grensesnittet av følgende klasser, som er avledet fra CSyncObject-klassen:

    CCriticalSection- implementerer den kritiske delen.

    CEvent- implementerer et hendelsesobjekt

    CMutex- implementerer en eksklusiv semafor.

    CSemafor- implementerer en klassisk semafor.

I tillegg til disse klassene, definerer MFC også to hjelpesynkroniseringsklasser: CSingleLock Og CMultiLock. De kontrollerer tilgangen til synkroniseringsobjektet og inneholder metoder som brukes til å gi og frigi slike objekter. Klasse CSingleLock kontrollerer tilgang til et enkelt synkroniseringsobjekt og klassen CMultiLock- til flere gjenstander. I det følgende vil vi kun vurdere klassen CSingleLock.

Når et hvilket som helst synkroniseringsobjekt er opprettet, kan tilgangen til det kontrolleres ved hjelp av klassen CSingleLock. For å gjøre dette må du først opprette et objekt av typen CSingleLock ved å bruke konstruktøren:

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

Den første parameteren sender en peker til et synkroniseringsobjekt, for eksempel en semafor. Verdien til den andre parameteren bestemmer om konstruktøren skal forsøke å få tilgang til dette objektet. Hvis denne parameteren ikke er null, vil tilgang fås, ellers vil det ikke bli gjort noen forsøk på å få tilgang. Hvis tilgang er gitt, deretter tråden som opprettet klasseobjektet CSingleLock, vil bli stoppet til det tilsvarende synkroniseringsobjektet frigjøres av metoden Låse opp klasse CSingleLock.

Når et objekt av typen CSingleLock opprettes, kan tilgangen til objektet pekt på av pObject kontrolleres ved hjelp av to funksjoner: Låse Og Låse opp klasse CSingleLock.

Metode Låse er ment å få tilgang til et objekt til et synkroniseringsobjekt. Tråden som kalte den er suspendert til metoden er fullført, det vil si til tilgang til ressursen er oppnådd. Verdien av parameteren bestemmer hvor lenge funksjonen vil vente på tilgang til det nødvendige objektet. Hver gang en metode fullføres vellykket, reduseres verdien til telleren som er knyttet til synkroniseringsobjektet med én.

Metode Låse opp Frigjør synkroniseringsobjektet, og lar andre tråder bruke ressursen. I den første versjonen av metoden økes verdien av telleren knyttet til dette objektet med én. I det andre alternativet bestemmer den første parameteren hvor mye denne verdien skal økes. Den andre parameteren peker på variabelen som forrige tellerverdi skal skrives inn i.

Når du jobber med en klasse CSingleLock Den generelle prosedyren for å kontrollere tilgang til en ressurs er:

    opprette et objekt av typen CSyncObj (for eksempel en semafor) som skal brukes til å kontrollere tilgangen til ressursen;

    ved å bruke det opprettede synkroniseringsobjektet, lag et objekt av typen CSingleLock;

    for å få tilgang til en ressurs, kall opp låsemetoden;

    få tilgang til en ressurs;

    Ring opp låsemetoden for å frigjøre ressursen.

Det følgende beskriver hvordan du oppretter og bruker semaforer og hendelsesobjekter. Når du forstår disse konseptene, kan du enkelt lære og bruke to andre typer synkroniseringsobjekter: kritiske seksjoner og mutexes.

Hallo! I dag vil vi fortsette å vurdere funksjonene til flertrådsprogrammering og snakke om trådsynkronisering.

Hva er "synkronisering"? Utenfor programmeringsområdet refererer dette til en slags oppsett som lar to enheter eller programmer fungere sammen. For eksempel kan en smarttelefon og datamaskin synkroniseres med en Google-konto, og en personlig konto på en nettside kan synkroniseres med kontoer på sosiale nettverk for å logge på med dem. Trådsynkronisering har en lignende betydning: den setter opp hvordan tråder samhandler med hverandre. I tidligere forelesninger har trådene våre levd og fungert atskilt fra hverandre. Den ene talte noe, den andre sov, den tredje viste noe på konsollen, men de samhandlet ikke med hverandre. I virkelige programmer er slike situasjoner sjeldne. Flere tråder kan aktivt arbeide for eksempel med samme datasett og endre noe i det. Dette skaper problemer. Tenk deg at flere tråder skriver tekst til samme sted - for eksempel en tekstfil eller konsollen. Denne filen eller konsollen blir i dette tilfellet en delt ressurs. Tråder vet ikke om hverandres eksistens, så de skriver ganske enkelt ned alt de kan klare i løpet av tiden som trådplanleggeren tildeler dem. I en nylig forelesning av kurset hadde vi et eksempel på hva dette ville føre til, la oss huske det: Årsaken ligger i det faktum at trådene fungerte med en delt ressurs, konsollen, uten å koordinere handlinger med hverandre. Hvis trådplanleggeren har tildelt tid til Thread-1, skriver den umiddelbart alt til konsollen. Hvilke andre tråder som allerede har klart å skrive eller ikke har hatt tid til å skrive er ikke viktig. Resultatet, som du kan se, er katastrofalt. Derfor ble et spesielt konsept introdusert i flertrådsprogrammering mutex (fra engelsk "mutex", "gjensidig ekskludering" - "gjensidig ekskludering"). Mutex-oppgave- gi en mekanisme slik at bare én tråd har tilgang til et objekt på et bestemt tidspunkt. Hvis Tråd-1 har skaffet seg mutexen til objekt A, vil ikke andre tråder ha tilgang til det for å endre noe i det. Inntil objekt A's mutex slippes, vil de gjenværende trådene bli tvunget til å vente. Eksempel fra det virkelige liv: forestill deg at du og 10 andre fremmede deltar på en trening. Du må bytte på å uttrykke ideer og diskutere noe. Men siden dere ser hverandre for første gang, for ikke å avbryte hverandre hele tiden og ikke gli inn i ståhei, bruker dere «snakkerball»-regelen: bare én person kan snakke - den som har ballen inne. hendene hans. På denne måten viser diskusjonen seg å være tilstrekkelig og fruktbar. Så en mutex er i hovedsak en slik ball. Hvis et objekts mutex er i hendene på én tråd, vil ikke andre tråder få tilgang til objektet. Du trenger ikke gjøre noe for å lage en mutex: den er allerede innebygd i Object-klassen, noe som betyr at hvert objekt i Java har en.

Hvordan den synkroniserte operatøren fungerer

La oss bli kjent med et nytt søkeord - synkronisert. Det markerer en viss del av koden vår. Hvis en kodeblokk er merket med det synkroniserte nøkkelordet, betyr det at blokken kun kan utføres av én tråd om gangen. Synkronisering kan implementeres på forskjellige måter. Lag for eksempel en hel synkronisert metode: public synchronized void doSomething() ( //...metodelogikk) Eller skriv en kodeblokk der synkronisering utføres på et eller annet objekt: offentlig klasse Main ( privat Objekt obj = nytt Objekt () ; offentlig void doNoe () ( synkronisert (obj) ( ) ) ) Betydningen er enkel. Hvis en tråd skriver inn en kodeblokk som er merket med ordet synkronisert, får den øyeblikkelig objektets mutex, og alle andre tråder som prøver å gå inn i samme blokk eller metode, blir tvunget til å vente til den forrige tråden fullfører arbeidet og frigjør Observere. Forresten! I kursforelesningene så du allerede eksempler på synkronisert , men de så annerledes ut: public void swap () ( synchronized (this) ( //...metodelogikk) ) Emnet er nytt for deg, og det vil selvfølgelig være forvirring med syntaksen i starten. Husk derfor med en gang for ikke å bli forvirret senere i skrivemetodene. Disse to måtene å skrive på betyr det samme: offentlig void swap () ( synkronisert (dette) ( //...metodelogikk) ) public synchronized void swap () ( ) ) I det første tilfellet oppretter du en synkronisert kodeblokk umiddelbart etter å ha angitt metoden. Det synkroniseres av dette objektet, det vil si av det gjeldende objektet. Og i det andre eksemplet setter du ordet synkronisert på hele metoden. Det er ikke lenger nødvendig å eksplisitt indikere noe objekt som synkronisering utføres på. Når en hel metode er merket med et ord, vil denne metoden automatisk bli synkronisert for alle objekter i klassen. La oss ikke fordype oss i diskusjonen om hvilken metode som er bedre. For nå, velg det du liker best :) Det viktigste er å huske: du kan kun erklære en metode synkronisert når all logikken i den utføres av én tråd samtidig. I dette tilfellet vil det for eksempel være en feil å gjøre doSomething()-metoden synkronisert: public class Main ( private Object obj = new Object () ; public void doSomething () ( //...noe logikk tilgjengelig for alle tråder synkronisert (obj) ( //logikk som bare er tilgjengelig for én tråd om gangen) ) ) Som du kan se, inneholder en del av metoden logikk som synkronisering ikke er nødvendig. Koden i den kan kjøres av flere tråder samtidig, og alle kritiske steder tildeles en separat synkronisert blokk. Og ett øyeblikk. La oss se under lupen på vårt navnebytteeksempel fra forelesning: public void swap () ( synkronisert (dette) ( //...metodelogikk } } Følg med: synkronisering utføres ved hjelp av denne . Det vil si på et spesifikt MyClass-objekt. Tenk deg at vi har 2 tråder (Thread-1 og Thread-2) og bare ett objekt MyClass myClass . I dette tilfellet, hvis Thread-1 kaller myClass.swap()-metoden, vil objektets mutex være opptatt, og Thread-2, når du prøver å kalle myClass.swap(), vil henge og vente på at mutexen blir ledig. Hvis vi har 2 tråder og 2 MyClass-objekter - myClass1 og myClass2 - på forskjellige objekter kan våre tråder enkelt utføre synkroniserte metoder samtidig. Den første tråden kjører: myClass1. bytte(); Den andre gjør det: myClass2. bytte(); I dette tilfellet vil ikke det synkroniserte nøkkelordet inne i swap()-metoden påvirke driften av programmet, siden synkronisering utføres på et spesifikt objekt. Og i sistnevnte tilfelle har vi 2 objekter.Derfor skaper ikke trådene problemer for hverandre. Tross alt to objekter har 2 forskjellige mutexes og deres anskaffelse er uavhengig av hverandre.

Funksjoner ved synkronisering i statiske metoder

Hva du skal gjøre hvis du trenger å synkronisere statisk metode? klasse MyClass ( privat statisk strengnavn1 = "Olya" ; privat statisk strengnavn2 = "Lena" ; offentlig statisk synkronisert void swap () ( streng s = navn1; navn1 = navn2; navn2 = s; ) ) Det er ikke klart hva som vil oppfylle rollen mutex i dette tilfellet. Tross alt har vi allerede bestemt at hvert objekt har en mutex. Men problemet er at for å kalle den statiske metoden MyClass.swap() trenger vi ikke objekter: metoden er statisk! Så, hva er neste? :/ Egentlig er det ikke noe problem med dette. Skaperne av Java tok seg av alt :) Hvis metoden som inneholder den kritiske "multithreaded" logikken er statisk, vil synkroniseringen bli utført etter klasse. For større klarhet kan koden ovenfor skrives om som: klasse MyClass ( privat statisk strengnavn1 = "Olya" ; privat statisk strengnavn2 = "Lena" ; offentlig statisk tomromsbytte () (synkronisert (MyClass. klasse ) ( String s = navn1 ; navn1 = navn2; navn2 = s; ) ) ) I prinsippet kunne du ha tenkt på dette på egen hånd: siden det ikke er noen objekter, må synkroniseringsmekanismen på en eller annen måte være "hardwired" inn i klassene selv. Sånn er det: Du kan også synkronisere på tvers av klasser.