Introduksjon til Lock Striping

1. Introduksjon

I denne opplæringen skal vi lære å oppnå finkornet synkronisering, også kjent som Lock Striping, et mønster for håndtering av samtidig tilgang til datastrukturer samtidig som vi holder en god ytelse.

2. Problemet

HashMap er ikke en trådsikker datastruktur på grunn av sin ikke-synkroniserte natur. Det betyr at kommandoer fra et miljø med flere tråder kan føre til inkonsistens i data.

For å løse dette problemet kan vi enten konvertere det originale kartet med Samlinger # synchronizedMap metoden eller bruk HashTable data struktur. Begge vil returnere en trådsikker implementering av Kart grensesnitt, men de koster ytelse.

Tilnærmingen til å definere eksklusivt tilgang over datastrukturer med et enkelt låsobjekt kalles grovkornet synkronisering.

I en grovkornet synkroniseringsimplementering må hver tilgang til objektet gjøres av gangen med en tråd. Vi ender opp med sekvensielle tilganger.

Målet vårt er å tillate samtidige tråder å jobbe med datastrukturen og samtidig sikre trådsikkerhet.

3. Lås striping

For å nå målet vårt, bruker vi Lock Striping-mønsteret. Låsestriping er en teknikk der låsingen skjer på flere bøtter eller striper, noe som betyr at tilgang til en bøtte bare låser den bøtta og ikke hele datastrukturen.

Det er et par måter å gjøre dette på:

  • Først kunne vi bruke en lås per oppgave, og dermed maksimere samtidigheten mellom oppgavene - dette har imidlertid et høyere minneutrykk
  • Eller vi kan bruke en enkelt lås for hver oppgave, som bruker mindre minne, men også kompromitterer ytelsen i samtid

For å hjelpe oss med å håndtere denne ytelsen-minne kompromiss, Guava leveres med en klasse kalt Stripete. Det ligner på logikk i ConcurrentHashMap, men Stripete klasse går enda lenger ved å redusere synkroniseringen av forskjellige oppgaver ved hjelp av semaforer eller reentrant låser.

4. Et raskt eksempel

La oss gjøre et raskt eksempel for å forstå fordelene med dette mønsteret.

Vi sammenligner HashMap vs. ConcurrentHashMap og en enkelt lås mot en stripet lås som resulterte i fire eksperimenter.

For hvert eksperiment vil vi utføre samtidig lesing og skriving på det underliggende Kart. Det som vil variere er hvordan vi får tilgang til hver bøtte.

Og for det lager vi to klasser - SingleLock og StripedLock. Dette er konkrete implementeringer av en abstrakt klasse ConcurrentAccessExperiment det gjør jobben.

4.1. Avhengigheter

Siden vi skal bruke Guava Stripete klasse, legger vi til guava avhengighet:

 com.google.guava guava 28.2-jre 

4.2. Hovedprosess

Våre ConcurrentAccessExperiment klasse implementerer oppførselen som er beskrevet tidligere:

offentlig abstrakt klasse ConcurrentAccessExperiment {public final Map doWork (Map map, int threads, int slots) {CompletableFuture [] requests = new CompletableFuture [threads * slots]; for (int i = 0; i <threads; i ++) {forespørsler [slots * i + 0] = CompletableFuture.supplyAsync (putSupplier (map, i)); forespørsler [slots * i + 1] = CompletableFuture.supplyAsync (getSupplier (kart, i)); forespørsler [slots * i + 2] = CompletableFuture.supplyAsync (getSupplier (kart, i)); forespørsler [slots * i + 3] = CompletableFuture.supplyAsync (getSupplier (kart, i)); } CompletableFuture.allOf (forespørsler). Bli med (); retur kart; } beskyttet abstrakt Leverandør putSupplier (Map map, int key); beskyttet abstrakt Leverandør getSupplier (kartkart, int-nøkkel); }

Det er viktig å merke seg at, siden testen vår er CPU-bundet, har vi begrenset antall bøtter til noen flere av de tilgjengelige prosessorer.

4.3. Samtidig tilgang med ReentrantLock

Nå implementerer vi metodene for de asynkrone oppgavene våre.

Våre SingleLock klasse definerer en enkelt lås for hele datastrukturen ved hjelp av en ReentrantLock:

offentlig klasse SingleLock utvider ConcurrentAccessExperiment {ReentrantLock lock; offentlig SingleLock () {lock = new ReentrantLock (); } beskyttet leverandør putSupplier (Map map, int key) {return (() -> {lock.lock (); prøv {return map.put ("key" + key, "value" + key);} til slutt {lock. låse opp ();}}); } beskyttet leverandør getSupplier (Map map, int key) {return (() -> {lock.lock (); prøv {return map.get ("key" + key);} til slutt {lock.unlock ();}} ); }}

4.4. Samtidig tilgang med Stripete

Og så StripedLock klasse definerer en stripet lås for hver bøtte:

offentlig klasse StripedLock utvider ConcurrentAccessExperiment {Striped lock; offentlig StripedLock (int bøtter) {lock = Striped.lock (bøtter); } beskyttet leverandør putSupplier (Map map, int key) {return (() -> {int bucket = key% stripedLock.size (); Lock lock = stripedLock.get (bucket); lock.lock (); prøv {return map .put ("nøkkel" + nøkkel, "verdi" + nøkkel);} til slutt {lock.unlock ();}}); } beskyttet leverandør getSupplier (kartkart, int-nøkkel) {return (() -> {int bucket = key% stripedLock.size (); Lock lock = stripedLock.get (bucket); lock.lock (); prøv {return map .get ("nøkkel" + nøkkel);} til slutt {lock.unlock ();}}); }}

Så hvilken strategi fungerer bedre?

5. Resultater

La oss bruke JMH (Java Microbenchmark Harness) for å finne ut av det. Referansene kan du finne via kildekodekoblingen på slutten av opplæringen.

Når vi kjører vår målestokk, kan vi se noe som ligner på følgende (merk at høyere gjennomstrømning er bedre):

Referansemodus Cnt Score feilenheter ConcurrentAccessBenchmark.singleLockConcurrentHashMap thrpt 10 0,059 ± 0,006 ops / ms ConcurrentAccessBenchmark.singleLockHashMap thrpt 10 0,061 ± 0,005 ops / ms ConcurrentAccessBenchmark.stripedLockConcurrentHashMap thrpt9 0,0 

6. Konklusjoner

I denne opplæringen undersøkte vi forskjellige måter for hvordan vi kan oppnå bedre ytelse ved hjelp av Lock Striping in Kart-lignende strukturer. Vi laget en referanse for å sammenligne resultatene med flere implementeringer.

Fra våre referanseresultater kan vi forstå hvordan forskjellige samtidige strategier kan påvirke den generelle prosessen betydelig. Striped Lock-mønsteret gir en ganske forbedring da det scorer ~ 10% ekstra med begge HashMap og ConcurrentHashMap.

Som vanlig er kildekoden for denne opplæringen tilgjengelig på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found