window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', versjon: 2, } ;(function () { var w = vindu if (w.LeadBooster) { console.warn('LeadBooster finnes allerede') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() FORKING OG THREADING I RUBY - The Codest
The Codest
  • Om oss
  • Tjenester
    • Programvareutvikling
      • Frontend-utvikling
      • Backend-utvikling
    • Staff Augmentation
      • Frontend-utviklere
      • Backend-utviklere
      • Dataingeniører
      • Ingeniører i skyen
      • QA-ingeniører
      • Annet
    • Det rådgivende
      • Revisjon og rådgivning
  • Industrier
    • Fintech og bankvirksomhet
    • E-commerce
    • Adtech
    • Helseteknologi
    • Produksjon
    • Logistikk
    • Bilindustrien
    • IOT
  • Verdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leveransesjef
  • Vårt team
  • Casestudier
  • Vet hvordan
    • Blogg
    • Møter
    • Webinarer
    • Ressurser
Karriere Ta kontakt med oss
  • Om oss
  • Tjenester
    • Programvareutvikling
      • Frontend-utvikling
      • Backend-utvikling
    • Staff Augmentation
      • Frontend-utviklere
      • Backend-utviklere
      • Dataingeniører
      • Ingeniører i skyen
      • QA-ingeniører
      • Annet
    • Det rådgivende
      • Revisjon og rådgivning
  • Verdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leveransesjef
  • Vårt team
  • Casestudier
  • Vet hvordan
    • Blogg
    • Møter
    • Webinarer
    • Ressurser
Karriere Ta kontakt med oss
Pil tilbake GÅ TILBAKE
2016-10-06
Programvareutvikling

FORKING OG THREADING I RUBY

Marek Gierlach

Som du sikkert vet, har Ruby noen få implementasjoner, for eksempel MRI, JRuby, Rubinius, Opal, RubyMotion etc., og hver av dem kan bruke et annet mønster for kodeutførelse. Denne artikkelen vil fokusere på de tre første av dem og sammenligne MRI

Som du sikkert vet, har Ruby noen få implementasjoner, for eksempel MRI, JRuby, Rubinius, Opal, RubyMotion etc., og hver av dem kan bruke et annet mønster av kode utførelse. Denne artikkelen vil fokusere på de tre første av dem og sammenligne MRI (for tiden den mest populære implementasjonen) med JRuby og Rubinius ved å kjøre noen eksempler på skript som skal vurdere egnetheten til forking og threading i ulike situasjoner, for eksempel behandling av CPU-intensive algoritmer, kopiering av filer osv.Før du begynner å "lære ved å gjøre", må du repetere noen grunnleggende begreper.

Gaffel

  • er en ny underordnet prosess (en kopi av den overordnede)
  • har en ny prosessidentifikator (PID)
  • har separat minne*
  • kommuniserer med andre via IPC-kanaler (interprosess-kommunikasjon) som meldingskøer, filer, sockets osv.
  • eksisterer selv når overordnet prosess avsluttes
  • er et POSIX-kall - fungerer hovedsakelig på Unix-plattformer

Tråd

  • er "bare" en utførelseskontekst som fungerer innenfor en prosess
  • deler alt minnet med andre (som standard bruker den mindre minne enn en gaffel)
  • kommuniserer med andre ved hjelp av delte minneobjekter
  • dør med en prosess
  • introduserer typiske multitrådings-problemer som starvation, deadlocks osv.

Det finnes mange verktøy som bruker forks og tråder, og som brukes daglig, f.eks. Unicorn (forks) og Puma (tråder) på applikasjonsservernivå, Resque (forks) og Sidekiq (tråder) på bakgrunnsjobbnivå osv.

Tabellen nedenfor viser støtten for forking og threading i de viktigste Ruby-implementeringene.

Implementering av RubyGaffelGjenging
MRJaJa (begrenset av GIL**)
JRuby–Ja
RubiniusJaJa

To andre magiske ord kommer tilbake som en bumerang i dette emnet - parallellisme og samtidighet - vi må forklare dem litt. Først og fremst kan ikke disse begrepene brukes om hverandre. Kort sagt - vi kan snakke om parallellitet når to eller flere oppgaver behandles nøyaktig samtidig. Samtidighet finner sted når to eller flere oppgaver behandles i overlappende tidsperioder (ikke nødvendigvis samtidig). Ja, det er en grov forklaring, men den er god nok til at du vil merke forskjellen og forstå resten av denne artikkelen.

Fronented-rapport for 2020

Tabellen nedenfor viser støtten for parallellisme og samtidighet.

Implementering av RubyParallellisme (via gafler)Parallellisme (via tråder)Samtidighet
MRJaNeiJa
JRuby–JaJa
RubiniusJaJa (siden versjon 2.X)Ja

Nå er det slutt på teorien - la oss se det i praksis!

  • Å ha separat minne betyr ikke nødvendigvis at man bruker like mye minne som den overordnede prosessen. Det finnes noen teknikker for minneoptimalisering. En av dem er Copy on Write (CoW), som gjør det mulig for en overordnet prosess å dele allokert minne med en underordnet prosess uten å kopiere det. Med CoW er det bare behov for ekstra minne hvis en underordnet prosess endrer det delte minnet. I Ruby-sammenheng er ikke alle implementasjoner CoW-vennlige, f.eks. har MRI støttet det fullt ut siden versjon 2.X. Før denne versjonen brukte hver fork like mye minne som en overordnet prosess.
  • En av de største fordelene/ulempene med MRI (stryk det upassende alternativet) er bruken av GIL (Global Interpreter Lock). Kort fortalt er denne mekanismen ansvarlig for å synkronisere kjøringen av tråder, noe som betyr at bare én tråd kan kjøres om gangen. Men vent ... Betyr det at det ikke er noe poeng i å bruke tråder i MR i det hele tatt? Svaret får du ved å forstå GILs interne funksjoner ... eller i det minste ta en titt på kodeeksemplene i denne artikkelen.

Testtilfelle

For å vise hvordan forking og threading fungerer i Rubys implementasjoner, har jeg laget en enkel klasse som heter Test og noen få andre som arver fra den. Hver klasse har en egen oppgave som skal behandles. Som standard kjører hver oppgave fire ganger i en løkke. Hver oppgave kjører også mot tre typer kodeutførelse: sekvensiell, med gafler og med tråder. I tillegg til dette, Benchmark.bmbm kjører kodeblokken to ganger - første gang for å få kjøretidsmiljøet i gang, andre gang for å måle. Alle resultatene som presenteres i denne artikkelen, ble oppnådd i den andre kjøringen. Selvfølgelig er selv bmbm metoden garanterer ikke perfekt isolasjon, men forskjellene mellom flere kodekjøringer er ubetydelige.

krever "benchmark"

klasse Test
  AMOUNT = 4

  def run
    Benchmark.bmbm do |b|
      b.report("sekvensiell") { sekvensiell }
      b.report("forking") { forking }
      b.report("threading") { threading }
    end
  end

  private

  def sekvensiell
    AMOUNT.times { utføre }
  end

  def forking
    AMOUNT.times do
      fork do
        perform
      end
    end

    Process.waitall
  rescue NotImplementedError => e
    # fork-metoden er ikke tilgjengelig i JRuby
    puts e
  end

  def threading
    threads = []

    AMOUNT.times do
      threads << Thread.new do
        perform
      end
    end

    threads.map(&:join)
  end

  def utføre
    raise "ikke implementert"
  end
end
Belastningstest

Kjører beregninger i en løkke for å generere stor CPU-belastning.

class LoadTest < Test
  def utføre
    1000.ganger { 1000.ganger { 2**3**4 } }
  end
end

La oss kjøre det...

LoadTest.new.run

...og sjekk resultatene

MRJRubyRubinius
sekvensiell1.8629282.0890001.918873
gafling0.945018–1.178322
gjenging1.9139821.1070001.213315

Som du kan se, er resultatene fra sekvensielle kjøringer like. Det er selvfølgelig en liten forskjell mellom løsningene, men det skyldes den underliggende implementeringen av de valgte metodene i de ulike tolkene.

I dette eksempelet gir forking en betydelig ytelsesgevinst (koden kjører nesten to ganger raskere).

Threading gir lignende resultater som forking, men bare for JRuby og Rubinius. Å kjøre eksempelet med tråder på MR bruker litt mer tid enn den sekvensielle metoden. Det er minst to grunner til det. For det første tvinger GIL sekvensiell kjøring av tråder, og i en perfekt verden burde kjøretiden derfor være den samme som for den sekvensielle kjøringen, men det oppstår også et tidstap for GIL-operasjoner (bytte mellom tråder osv.). For det andre trengs det også noe overheadtid for å opprette tråder.

Dette eksemplet gir oss ikke noe svar på spørsmålet om betydningen av brukstråder i MR. La oss se på et annet.

Snooze-test

Kjører en hvilemetode.

class SnoozeTest < Test
  def utføre
    sleep 1
  end
end

Her er resultatene

MRJRubyRubinius
sekvensiell4.0046204.0060004.003186
gafling1.022066–1.028381
gjenging1.0015481.0040001.003642

Som du kan se, gir hver implementering lignende resultater, ikke bare i sekvensielle og forking-kjøringer, men også i trådkjøringene. Så hvorfor har MRI samme ytelsesgevinst som JRuby og Rubinius? Svaret ligger i implementeringen av sove.

MR-undersøkelser sove metoden er implementert med rb_thread_wait_for C-funksjon, som bruker en annen funksjon kalt native_sleep. La oss ta en rask titt på implementasjonen (koden ble forenklet, den opprinnelige implementasjonen finnes her):

statisk ugyldig
native_sleep(rb_thread_t *th, struct timeval *timeout_tv)
{
  ...

  GVL_UNLOCK_BEGIN();
  {
    // gjør noen ting her
  }
  GVL_UNLOCK_END();

  thread_debug("native_sleep donen");
 }

Grunnen til at denne funksjonen er viktig, er at den i tillegg til å bruke en streng Ruby-kontekst, også bytter til systemkonteksten for å utføre noen operasjoner der. I slike situasjoner har ikke Ruby-prosessen noe å gjøre... Et godt eksempel på bortkastet tid? Egentlig ikke, fordi det er en GIL som sier: "Ingenting å gjøre i denne tråden? La oss bytte til en annen og komme tilbake hit etter en stund". Dette kan gjøres ved å låse opp og låse GIL med GVL_UNLOCK_BEGIN() og GVL_UNLOCK_END() funksjoner.

Situasjonen blir klar, men sove metoden er sjelden nyttig. Vi trenger flere eksempler fra det virkelige liv.

Test av nedlasting av filer

Kjører en prosess som laster ned og lagrer en fil.

krever "net/http"

class DownloadFileTest < Test
  def utføre
    Net::HTTP.get("upload.wikimedia.org", "/wikipedia/commons/thumb/7/73/Ruby_logo.svg/2000px-Ruby_logo.svg.png")
  slutt
end

Det er ikke nødvendig å kommentere de følgende resultatene. De er ganske like dem fra eksempelet ovenfor.

1.003642JRubyRubinius
sekvensiell0.3279800.3340000.329353
gafling0.104766–0.121054
gjenging0.0857890.0940000.088490

Et annet godt eksempel kan være filkopieringsprosessen eller andre I/O-operasjoner.

Konklusjoner

  • Rubinius støtter fullt ut både forking og threading (siden versjon 2.X, da GIL ble fjernet). Koden din kan være samtidig og kjøre parallelt.
  • JRuby gjør en god jobb med tråder, men støtter ikke forking i det hele tatt. Parallellisme og samtidighet kan oppnås med tråder.
  • MR støtter forking, men tråding er begrenset av tilstedeværelsen av GIL. Samtidighet kan oppnås med tråder, men bare når kjørende kode går utenfor Ruby-tolkerens kontekst (f.eks. IO-operasjoner, kjernefunksjoner). Det finnes ingen måte å oppnå parallellitet på.

Relaterte artikler

Programvareutvikling

Bygg fremtidssikre webapper: Innsikt fra The Codests ekspertteam

Oppdag hvordan The Codest utmerker seg når det gjelder å skape skalerbare, interaktive webapplikasjoner med banebrytende teknologi som gir sømløse brukeropplevelser på tvers av alle plattformer. Finn ut hvordan ekspertisen vår driver digital transformasjon og...

THECODEST
Programvareutvikling

Topp 10 Latvia-baserte programvareutviklingsselskaper

I vår nyeste artikkel kan du lese mer om Latvias beste programvareutviklingsselskaper og deres innovative løsninger. Oppdag hvordan disse teknologilederne kan bidra til å løfte virksomheten din.

thecodest
Løsninger for bedrifter og oppskalering

Grunnleggende om Java-programvareutvikling: En guide til vellykket outsourcing

Utforsk denne viktige veiledningen om vellykket outsourcing av Java-programvareutvikling for å øke effektiviteten, få tilgang til ekspertise og drive frem prosjektsuksess med The Codest.

thecodest
Programvareutvikling

Den ultimate guiden til outsourcing i Polen

Den kraftige økningen i outsourcing i Polen er drevet av økonomiske, utdanningsmessige og teknologiske fremskritt, noe som fremmer IT-vekst og et forretningsvennlig klima.

TheCodest
Løsninger for bedrifter og oppskalering

Den komplette guiden til verktøy og teknikker for IT-revisjon

IT-revisjoner sørger for sikre, effektive og kompatible systemer. Les hele artikkelen for å lære mer om viktigheten av dem.

The Codest
Jakub Jakubowicz CTO og medgrunnlegger

Abonner på vår kunnskapsbase og hold deg oppdatert på ekspertisen fra IT-sektoren.

    Om oss

    The Codest - Internasjonalt programvareutviklingsselskap med teknologisentre i Polen.

    Storbritannia - Hovedkvarter

    • Kontor 303B, 182-184 High Street North E6 2JA
      London, England

    Polen - Lokale teknologisentre

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warszawa, Polen

      The Codest

    • Hjem
    • Om oss
    • Tjenester
    • Casestudier
    • Vet hvordan
    • Karriere
    • Ordbok

      Tjenester

    • Det rådgivende
    • Programvareutvikling
    • Backend-utvikling
    • Frontend-utvikling
    • Staff Augmentation
    • Backend-utviklere
    • Ingeniører i skyen
    • Dataingeniører
    • Annet
    • QA-ingeniører

      Ressurser

    • Fakta og myter om samarbeid med en ekstern programvareutviklingspartner
    • Fra USA til Europa: Hvorfor velger amerikanske oppstartsbedrifter å flytte til Europa?
    • Sammenligning av Tech Offshore Development Hubs: Tech Offshore Europa (Polen), ASEAN (Filippinene), Eurasia (Tyrkia)
    • Hva er de største utfordringene for CTO-er og CIO-er?
    • The Codest
    • The Codest
    • The Codest
    • Retningslinjer for personver
    • Vilkår for bruk av nettstedet

    Opphavsrett © 2025 av The Codest. Alle rettigheter forbeholdt.

    nb_NONorwegian
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek nb_NONorwegian