Operațiuni atomice și non-atomice (java) - saturație în rusă

Cum să înțelegem ce operații sunt atomice și care sunt non-atomice?

O operație într-o zonă de memorie comună se numește atomică dacă este terminată într-un singur pas față de alte fire care au acces la această memorie. În timpul executării unei astfel de operațiuni pe o variabilă, niciun fir nu poate observa o schimbare pe jumătate completă. Încărcarea atomică asigură că variabila va fi încărcată în întregime la un moment dat. Operațiile non-atomice nu oferă o astfel de garanție.







Ie după cum am înțeles, operațiunile atomice sunt destul de mici, "cu un pas înaintea altor fluxuri". Dar ce înseamnă acest "pas"?

Un pas == o operație a mașinii? Sau altceva? Cum să determinăm exact ce operații sunt atomice și care sunt non-atomice?

P.S. Am găsit o întrebare similară. dar acolo este o chestiune de C #.

Cum poți defini atomicitatea?

Atomicitatea unei operațiuni este cel mai frecvent indicată de semnul său de indivizibilitate: operațiunea poate fi aplicată complet sau deloc. Un exemplu bun ar fi să scrieți valori într-o matrice:

Când se utilizează metoda nonAtomic există posibilitatea ca unele thread va apela la array [0] la un moment în matrice [0] nu este inițializat, și va primi o valoare neașteptată. Când se utilizează probablyAtomic (cu condiția ca matrice este mai întâi umplut, și apoi mai târziu atribuite - acum nu pot garanta că în Java acesta este cazul, dar ne imaginăm că această regulă se aplică în exemplul), acest lucru nu ar trebui să fie: matrice conține întotdeauna sau nul sau inițializat matrice, dar în matrice [0] nu poate conține altceva decât 1. această operațiune este indivizibil și nu poate fi aplicată în jumătate, așa cum era cu nonAtomic - numai sau complet, sau deloc, iar restul codul se poate aștepta cu siguranță că matrice este fie nulă, sau valoarea Eu, fără verificări suplimentare.

În plus, atomicitatea unei operațiuni implică deseori vizibilitatea rezultatului său pentru toți participanții la sistemul în care se aplică (în acest caz, fluxurile); acest lucru este logic, dar, în opinia mea, nu este un semn obligatoriu al atomicității.

De ce este important acest lucru?

Atomicitate provine adesea dintr-o aplicație de afaceri cerințe: tranzacții bancare trebuie să fie aplicate pe deplin, bilete la concert pentru a fi comandate direct în cantitățile, care au fost specificate, etc. Este în acest context, care înțelege (multithreading în Java), sarcinile sunt mai primitive, dar rezultă din aceleași cerințe: de exemplu, în cazul în care este scris aplicație web, analizarii HTTP-cerere, serverul trebuie să aibă toate interogările primite cu adăugarea atomică, în caz contrar, există riscul pierderea cererilor primite, și, prin urmare, degradarea calității serviciului. operații atomice oferă garanții (indivizibile), iar acestea ar trebui să se recurgă la atunci când sunt necesare aceste garanții.

În plus, operațiile atomice sunt liniarizabile - în general, implementarea lor poate fi descompusă într-o singură istorie liniară, în timp ce doar operațiunile pot produce un grafic al povestilor, care în unele cazuri este inacceptabil.

De ce operațiile primitive nu sunt atomice în sine? Ar fi, de asemenea, mai ușor pentru toată lumea.

Mediile de execuție moderne sunt foarte complexe și au o mulțime de optimizări la bord care pot fi făcute cu codul, dar în cele mai multe cazuri aceste optimizări încalcă garanțiile. Deoarece cea mai mare parte a codului acestor garanții nu necesită de fapt, a fost mai ușor să se identifice tranzacțiile cu garanții specifice într-o clasă separată, mai degrabă decât invers. Cel mai frecvent exemplu este schimbarea ordinii expresiilor - procesorul și JVM au dreptul să execute expresiile în ordinea greșită în care au fost descrise în cod, până când programatorul accelerează o anumită ordine de execuție prin operații cu garanții specifice. De asemenea, puteți da un exemplu (dar nu este sigur că este corect din punct de vedere formal) cu citirea valorii din memorie:







Nu folosește așa-numitul. sursă singulară de adevăr pentru a controla valoarea lui X, astfel încât astfel de anomalii sunt posibile. În măsura în care înțeleg, citirea și scrierea directă în memorie (sau în memorie și într-o memorie cache a procesorului comun) este exact ceea ce forțele modificatoare volatile (s-ar putea să fiu greșit aici).

Desigur, codul optimizat rulează mai repede, însă garanțiile necesare nu trebuie niciodată sacrificate pentru performanța codului.

Aceasta se aplică numai operațiilor legate de instalarea variabilelor și a altor domenii de activitate ale procesorului?

Nu, nu este. Orice operație poate fi atomică sau non-atomică, de exemplu, bazele de date relaționale clasice garantează că o tranzacție - care poate consta în modificări de date pe megabyte - va fi aplicată pe deplin sau nu va fi aplicată. Instrucțiunile procesorului de aici sunt irelevante; operația poate fi atomică atât timp cât ea însăși este atomică sau rezultatul acesteia se manifestă sub forma unei alte operații atomice (de exemplu, rezultatul unei tranzacții de bază de date se manifestă în scris într-un fișier).

În plus, în măsura în care înțeleg, afirmația că "instrucțiunea nu avea timp într-un singur ciclu - operația nu este atomică" este, de asemenea, incorectă, deoarece există instrucțiuni specializate. și nimeni nu intervine în mod atomic pentru a seta vreo valoare în memorie la intrarea în blocul protejat și să-l tragem pe ieșire.

Poate orice operație să fie atomică?

Nu, nu este. Am foarte mult nu au competențe suficiente pentru a corecta textul, dar în măsura în care am înțeles, orice operațiune care implică două sau mai multe efecte externe (efecte secundare), nu poate fi atomică prin definiție. Prin efect secundar implică în primul rând, interacțiunea cu orice sistem extern (indiferent dacă este vorba de un sistem de fișiere sau un API extern), dar chiar și cele două declarații set de variabile din interiorul blocului de sincronizat nu poate fi considerată o operațiune atomică, în timp ce una dintre ele poate arunca o excepție - și , ținând seama de OutOfMemoryError și alte rezultate posibile, poate fi imposibilă.

Am o operație cu două sau mai multe efecte secundare. Pot să fac ceva în legătură cu asta?

Da, puteți crea un sistem care să garanteze utilizarea tuturor operațiunilor, dar cu condiția ca orice efect secundar să poată fi numit de nenumărate ori. Puteți crea un sistem de jurnalizare care înregistrează în mod atomic operațiunile programate, verifică în mod regulat împotriva jurnalului și efectuează ceea ce nu a fost încă aplicat. Aceasta poate fi reprezentată după cum urmează:

Acest lucru asigură progresul algoritmului, dar elimină toate obligațiile privind intervalul de timp (cu care, formal vorbind, totul nu este în regulă). În cazul în care operațiunile sunt idempotent, un astfel de sistem va ajunge, mai devreme sau mai târziu, la starea solicitată, fără diferențe vizibile față de cele așteptate (cu excepția timpului de execuție).

Cum se determină atomicitatea operațiilor în Java?

Cum să înțelegem ce operații sunt atomice și care sunt non-atomice?

La riscul de a suporta taxele sexismului, nu ma abtin si sa dea un exemplu de operație atomică: sarcină - operațiune strict atomică, există întotdeauna unul și numai un singur tată (tot felul de trucuri de gene scoase din paranteze).

Pe de altă parte, un exemplu non-atomice operațiuni: copii - din păcate, operațiune non-atomice, copilul are, din păcate, obiectul unui număr de diferite operații non-sincronizate asupra sufletului fragil al copilului: mama, tata, bunica, bunicul, zomboyaschik, gradinita, scoala, prieteni, prietene, etc. pe listă.

Postat pe 24 ianuarie, la ora 11:58

Funny comparație)))) - Ksenia 24 ianuarie, la 3:10 pm

Probabil ați însemnat fertilizarea. de la care începe sarcina. Un exemplu foarte rapid, da. - D-side 25 ianuarie, la 2:21 pm

În general, da, am vorbit în mod deliberat cât mai neutru posibil :) - Barmaley 25 ianuarie, la 5:58 am

Voi încerca să-ți explic. Pot să mă înșel.

Există java, codurile sursă sunt compilate în octet. Bytecode la timpul de execuție este convertită la codul mașinii. O instrucțiune / instrucțiune în bytecode poate fi transformată în mai multe instrucțiuni de cod mașină. Aceasta este problema atomicității. Procesorul nu poate executa o comandă scrisă într-un limbaj de nivel înalt la un moment dat. execută codul mașinii care conține o secvență de comenzi. Prin urmare, dacă procesoare diferite efectuează manipulări pe aceleași date, pot fi alternate instrucțiuni diferite de procesor.

Există o variabilă globală:

Creșterea unei variabile nu este o operație atomică: aceasta necesită cel puțin trei instrucțiuni:

  • citiți datele
  • crește cu unul
  • scrieți date

În consecință, două fire trebuie să execute această secvență, dar ordinea execuției lor nu este definită între ele. Din această cauză pot apărea situații precum:

  1. Primul fir a citit datele
  2. Al doilea fir a citit datele
  3. Primul fir a mărit valoarea cu 1
  4. Al doilea fir a mărit valoarea cu 1
  5. Al doilea flux a înregistrat valoarea
  6. Primul flux a înregistrat valoarea

Ca rezultat, avem rezultatul 1, nu 2 așa cum era de așteptat.

Pentru a evita acest lucru, utilizați primitive de sincronizare sau atomice din pachetul java.util.concurrent

@etki este corectă, problema este că nu toate instrucțiunile sunt executate într-un singur ciclu de ceas. Și dacă calculatorul are nevoie de o operație care să fie efectuată pentru mai multe cicluri, se poate opri la mijlocul acestei operații și poate trece la următoarea. De exemplu, o valoare dintr-o anumită variabilă i a intrat în registru, după care comutatorul trece la o altă sarcină și scrie valoarea registrului la managerul de context. Când se întoarce la sarcină, el va reveni la registru cu valoarea trecută, indiferent de ceea ce este egal cu i. - faoxis 18 ianuarie, ora 10:43







Articole similare

Trimiteți-le prietenilor: