window.pipedriveLeadboosterConfig = { bas: 'leadbooster-chat.pipedrive.com', företagId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(funktion () { var w = fönster if (w.LeadBooster) { console.warn('LeadBooster finns redan') } annars { w.LeadBooster = { q: [], on: funktion (n, h) { this.q.push({ t: "o", n: n, h: h }) }, trigger: funktion (n) { this.q.push({ t: 't', n: n }) }, } } })() GAFFLING OCH TRÅDNING I RUBY - The Codest
Codest
  • Om oss
  • Tjänster
    • Utveckling av programvara
      • Frontend-utveckling
      • Backend-utveckling
    • Staff Augmentation
      • Frontend-utvecklare
      • Backend-utvecklare
      • Dataingenjörer
      • Ingenjörer inom molntjänster
      • QA-ingenjörer
      • Övriga
    • Det rådgivande
      • Revision och rådgivning
  • Industrier
    • Fintech & bankverksamhet
    • E-commerce
    • Adtech
    • Hälsoteknik
    • Tillverkning
    • Logistik
    • Fordon
    • IOT
  • Värde för
    • VD OCH KONCERNCHEF
    • CTO
    • Leveranschef
  • Vårt team
  • Fallstudier
  • Vet hur
    • Blogg
    • Möten
    • Webbinarier
    • Resurser
Karriär Ta kontakt med oss
  • Om oss
  • Tjänster
    • Utveckling av programvara
      • Frontend-utveckling
      • Backend-utveckling
    • Staff Augmentation
      • Frontend-utvecklare
      • Backend-utvecklare
      • Dataingenjörer
      • Ingenjörer inom molntjänster
      • QA-ingenjörer
      • Övriga
    • Det rådgivande
      • Revision och rådgivning
  • Värde för
    • VD OCH KONCERNCHEF
    • CTO
    • Leveranschef
  • Vårt team
  • Fallstudier
  • Vet hur
    • Blogg
    • Möten
    • Webbinarier
    • Resurser
Karriär Ta kontakt med oss
Pil tillbaka GÅ TILLBAKA
2016-10-06
Utveckling av programvara

FÖRGRENING OCH TRÅDNING I RUBY

Marek Gierlach

Som du säkert vet har Ruby några implementeringar, till exempel MRI, JRuby, Rubinius, Opal, RubyMotion etc., och var och en av dem kan använda ett annat mönster för kodkörning. Den här artikeln kommer att fokusera på de tre första av dem och jämföra MRI

Som du säkert vet har Ruby några implementeringar, till exempel MRI, JRuby, Rubinius, Opal, RubyMotion etc., och var och en av dem kan använda ett annat mönster för kod utförande. Den här artikeln fokuserar på de tre första av dem och jämför MRI (för närvarande den mest populära implementeringen) med JRuby och Rubinius genom att köra några exempelskript som ska bedöma lämpligheten av forking och threading i olika situationer, t.ex. bearbetning av CPU-intensiva algoritmer, kopiering av filer etc.Innan du börjar "lära dig genom att göra" måste du repetera några grundläggande termer.

Gaffel

  • är en ny underordnad process (en kopia av den överordnade)
  • har en ny processidentifierare (PID)
  • har separat minne*
  • kommunicerar med andra via IPC-kanaler (inter-process communication) som t.ex. meddelandeköer, filer, sockets etc.
  • existerar även när föräldraprocessen avslutas
  • är ett POSIX-anrop - fungerar främst på Unix-plattformar

Tråd

  • är "bara" en utförandekontext, som arbetar inom en process
  • delar allt minne med andra (som standard använder den mindre minne än en gaffel)
  • kommunicerar med andra genom delade minnesobjekt
  • dör med en process
  • introducerar typiska multi-threading-problem såsom starvation, deadlocks etc.

Det finns gott om verktyg som använder forkar och trådar och som används dagligen, t.ex. Unicorn (forkar) och Puma (trådar) på applikationsservernivå, Resque (forkar) och Sidekiq (trådar) på bakgrundsjobbnivå osv.

Följande tabell visar stödet för forking och threading i de viktigaste Ruby-implementeringarna.

Implementering av RubyGafflingGängning
MRTJaJa (begränsat av GIL**)
JRuby–Ja
RubiniusJaJa

Ytterligare två magiska ord kommer tillbaka som en bumerang i det här ämnet - parallellism och samtidighet - vi måste förklara dem lite. Först och främst kan dessa termer inte användas omväxlande. I ett nötskal - vi kan tala om parallellism när två eller flera uppgifter bearbetas exakt samtidigt. Samtidighet uppstår när två eller flera uppgifter bearbetas under överlappande tidsperioder (inte nödvändigtvis samtidigt). Ja, det är en bred förklaring, men tillräckligt bra för att hjälpa dig att märka skillnaden och förstå resten av den här artikeln.

Fronented-rapport för 2020

I följande tabell presenteras stödet för parallellism och samtidighet.

Implementering av RubyParallellism (via gafflar)Parallellism (via trådar)Samtidighet
MRTJaNejJa
JRuby–JaJa
RubiniusJaJa (sedan version 2.X)Ja

Nu är det slut på teorin - nu ska vi se hur det fungerar i praktiken!

  • Att ha separat minne behöver inte innebära att man använder samma mängd minne som den överordnade processen. Det finns några tekniker för minnesoptimering. En av dem är Copy on Write (CoW), som gör det möjligt för en överordnad process att dela allokerat minne med en underordnad process utan att kopiera det. Med CoW behövs ytterligare minne endast om en barnprocess ändrar det delade minnet. I Ruby-sammanhang är inte alla implementeringar CoW-vänliga, t.ex. stöder MRI det fullt ut sedan version 2.X. Före denna version förbrukade varje fork lika mycket minne som en överordnad process.
  • En av de största fördelarna/nackdelarna med MRI (stryk det olämpliga alternativet) är användningen av GIL (Global Interpreter Lock). I ett nötskal är denna mekanism ansvarig för att synkronisera körning av trådar, vilket innebär att endast en tråd kan köras åt gången. Men vänta ... Betyder det att det inte finns någon mening med att använda trådar i MRI alls? Svaret kommer med förståelsen av GIL-interna ... eller åtminstone ta en titt på kodproverna i den här artikeln.

Testfall

För att visa hur forking och threading fungerar i Rubys implementeringar skapade jag en enkel klass som heter Test och några andra som ärver från den. Varje klass har olika uppgifter som ska bearbetas. Som standard körs varje uppgift fyra gånger i en loop. Dessutom körs varje uppgift mot tre typer av kodkörning: sekventiell, med förgreningar och med trådar. Och dessutom Riktmärke.bmbm kör kodblocket två gånger - första gången för att få igång körtidsmiljön och andra gången för att mäta. Alla resultat som presenteras i den här artikeln erhölls under den andra körningen. Naturligtvis kan även bmbm metoden garanterar inte perfekt isolering, men skillnaderna mellan flera kodkörningar är obetydliga.

kräver "benchmark"

klass Test
  BELOPP = 4

  def kör
    Benchmark.bmbm do |b|
      b.report("sekventiell") { sekventiell }
      b.report("forking") { forking }
      b.report("threading") { threading }
    slut
  slut

  privat

  def sekventiell
    AMOUNT.times { utföra }
  slut

  def förgrening
    AMOUNT.gånger do
      gaffla do
        utföra
      slut
    slut

    Process.waitall
  rescue NotImplementedError => e
    # Fork-metoden är inte tillgänglig i JRuby
    sätter e
  slut

  def trådning
    trådar = []

    AMOUNT.times do
      trådar << Tråd.ny do
        utföra
      avsluta
    slut

    trådar.map(&:join)
  slut

  def utföra
    raise "inte implementerad"
  slut
slut
Belastningstest

Kör beräkningar i en loop för att generera stor CPU-belastning.

klass LoadTest < Test
  def utföra
    1000.gånger { 1000.gånger { 2**3**4 } }
  slut
slut

Låt oss köra det...

LoadTest.new.run

...och kontrollera resultatet

MRTJRubyRubinius
sekventiell1.8629282.0890001.918873
gaffling0.945018–1.178322
gängning1.9139821.1070001.213315

Som du kan se är resultaten från sekventiella körningar likartade. Naturligtvis finns det en liten skillnad mellan lösningarna, men den orsakas av den underliggande implementeringen av valda metoder i olika tolkar.

I det här exemplet ger forking en betydande prestandavinst (koden körs nästan två gånger snabbare).

Threading ger liknande resultat som forking, men endast för JRuby och Rubinius. Att köra provet med trådar på MRT tar lite mer tid än den sekventiella metoden. Det finns åtminstone två anledningar. För det första tvingar GIL sekventiell exekvering av trådar, så i en perfekt värld borde exekveringstiden vara densamma som för den sekventiella körningen, men det uppstår också en tidsförlust för GIL-operationer (växling mellan trådar etc.). För det andra behövs det också en del overheadtid för att skapa trådar.

Det här exemplet ger oss inte svar på frågan om innebörden av användningstrådar i MRT. Låt oss se ett annat.

Snooze-test

Kör en sömnmetod.

klass SnoozeTest < Test
  def utföra
    sova 1
  slut
slut

Här är resultaten

MRTJRubyRubinius
sekventiell4.0046204.0060004.003186
gaffling1.022066–1.028381
gängning1.0015481.0040001.003642

Som du kan se ger varje implementering liknande resultat, inte bara i de sekventiella och förgrenade körningarna, utan också i de trådade. Så varför har MRI samma prestandavinst som JRuby och Rubinius? Svaret ligger i implementeringen av Sova.

Magnetröntgen Sova metoden är implementerad med rb_tråd_vakta_för C-funktion, som använder en annan funktion som heter inbyggd_sömn. Låt oss ta en snabb titt på dess implementering (koden har förenklats, den ursprungliga implementeringen kan hittas här):

statiskt void
native_sleep(rb_thread_t *th, struct timeval *timeout_tv)
{
  ...

  GVL_UNLOCK_BEGIN();
  {
    // gör några saker här
  }
  GVL_UNLOCK_END();

  thread_debug("native_sleep donen");
 }

Anledningen till att denna funktion är viktig är att förutom att använda strikt Ruby-kontext, växlar den också till systemkontexten för att utföra vissa operationer där. I situationer som denna har Ruby-processen inget att göra ... Bra exempel på slöseri med tid? Inte riktigt, för det finns en GIL som säger: "Inget att göra i den här tråden? Låt oss byta till en annan och komma tillbaka hit efter ett tag". Detta kan göras genom att låsa upp och låsa GIL med GVL_UNLOCK_BEGIN() och GVL_UNLOCK_END() funktioner.

Situationen blir tydlig, men Sova metoden är sällan användbar. Vi behöver fler exempel från verkligheten.

Test av nedladdning av filer

Startar en process som hämtar och sparar en fil.

kräver "net/http"

klass DownloadFileTest < Test
  def utföra
    Net::HTTP.get("upload.wikimedia.org","/wikipedia/commons/thumb/7/73/Ruby_logo.svg/2000px-Ruby_logo.svg.png")
  slut
slut

Det finns inget behov av att kommentera följande resultat. De är ganska lika dem från exemplet ovan.

1.003642JRubyRubinius
sekventiell0.3279800.3340000.329353
gaffling0.104766–0.121054
gängning0.0857890.0940000.088490

Ett annat bra exempel kan vara filkopieringsprocessen eller någon annan I/O-operation.

Slutsatser

  • Rubinius har fullt stöd för både forking och threading (sedan version 2.X, då GIL togs bort). Din kod kan vara samtidig och köras parallellt.
  • JRuby gör ett bra jobb med trådar, men stöder inte forking alls. Parallellism och samtidighet kan uppnås med trådar.
  • MRT stöder forking, men trådning begränsas av förekomsten av GIL. Samtidighet kan uppnås med trådar, men endast när körkoden går utanför Ruby-tolkens kontext (t.ex. IO-operationer, kärnfunktioner). Det finns inget sätt att uppnå parallellism.

Relaterade artiklar

Utveckling av programvara

Bygg framtidssäkrade webbappar: Insikter från The Codest:s expertteam

Upptäck hur The Codest utmärker sig genom att skapa skalbara, interaktiva webbapplikationer med banbrytande teknik som ger sömlösa användarupplevelser på alla plattformar. Läs om hur vår expertis driver digital omvandling och affärsutveckling...

DEKODEST
Utveckling av programvara

Topp 10 Lettlandsbaserade mjukvaruutvecklingsföretag

Läs mer om Lettlands främsta mjukvaruutvecklingsföretag och deras innovativa lösningar i vår senaste artikel. Upptäck hur dessa teknikledare kan hjälpa till att lyfta ditt företag.

thecodest
Lösningar för företag och uppskalningsföretag

Java Software Development Essentials: En guide till framgångsrik outsourcing

Utforska denna viktiga guide om framgångsrik outsourcing av Java-programvaruutveckling för att förbättra effektiviteten, få tillgång till expertis och driva projektframgång med The Codest.

thecodest
Utveckling av programvara

Den ultimata guiden till outsourcing i Polen

Den kraftiga ökningen av outsourcing i Polen drivs av ekonomiska, utbildningsmässiga och tekniska framsteg, vilket främjar IT-tillväxt och ett företagsvänligt klimat.

TheCodest
Lösningar för företag och uppskalningsföretag

Den kompletta guiden till verktyg och tekniker för IT-revision

IT-revisioner säkerställer säkra, effektiva och kompatibla system. Läs mer om hur viktiga de är genom att läsa hela artikeln.

Codest
Jakub Jakubowicz CTO och medgrundare

Prenumerera på vår kunskapsbas och håll dig uppdaterad om expertisen från IT-sektorn.

    Om oss

    The Codest - Internationellt mjukvaruutvecklingsföretag med teknikhubbar i Polen.

    Förenade kungariket - Huvudkontor

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

    Polen - Lokala tekniknav

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

      Codest

    • Hem
    • Om oss
    • Tjänster
    • Fallstudier
    • Vet hur
    • Karriär
    • Ordbok

      Tjänster

    • Det rådgivande
    • Utveckling av programvara
    • Backend-utveckling
    • Frontend-utveckling
    • Staff Augmentation
    • Backend-utvecklare
    • Ingenjörer inom molntjänster
    • Dataingenjörer
    • Övriga
    • QA-ingenjörer

      Resurser

    • Fakta och myter om att samarbeta med en extern partner för mjukvaruutveckling
    • Från USA till Europa: Varför väljer amerikanska startup-företag att flytta till Europa?
    • Jämförelse av Tech Offshore Development Hubs: Tech Offshore Europa (Polen), ASEAN (Filippinerna), Eurasien (Turkiet)
    • Vilka är de största utmaningarna för CTO:er och CIO:er?
    • Codest
    • Codest
    • Codest
    • Privacy policy
    • Användarvillkor för webbplatsen

    Copyright © 2025 av The Codest. Alla rättigheter reserverade.

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