Samtidigt Haskell

Concurrent Haskell utökar Haskell 98 med explicit samtidighet . Dess två huvudsakliga underliggande koncept är:

  • En primitiv typ MVar α som implementerar en avgränsad/enplats asynkron kanal , som antingen är tom eller har ett värde av typen α .
  • Möjligheten att skapa en samtidig tråd via forkIO- primitiven.

Uppbyggd ovanpå detta är en samling användbara samtidighets- och synkroniseringsabstraktioner såsom obegränsade kanaler , semaforer och exempelvariabler.

Haskell-trådar har mycket låg overhead: skapande, kontextväxling och schemaläggning är alla interna i Haskell-körtiden. Dessa trådar på Haskell-nivå mappas till ett konfigurerbart antal trådar på OS-nivå, vanligtvis en per processorkärna .

Programvarutransaktionsminne

Programvarutransaktionsminnet (STM)-tillägget till GHC återanvänder processen för att forfla primitiver från Concurrent Haskell . STM dock:

STM-monad

STM- monaden är en implementering av mjukvarutransaktionsminne i Haskell. Det är implementerat i GHC och gör det möjligt att ändra föränderliga variabler i transaktioner .

Traditionellt tillvägagångssätt

Betrakta en bankapplikation som ett exempel, och en transaktion i den - överföringsfunktionen, som tar pengar från ett konto och sätter dem på ett annat konto. I IO-monaden kan detta se ut så här:

    

         
     
         
         
        
         typ  Konto  =  IORef  Heltalsöverföring  ::  Heltal  ->  Konto  -  >  Konto  ->  IO  ()  överföringsbelopp  från  till  =  gör  fromVal  <-  readIORef  from  -- (A)  toVal  <  -  readIORef  to  writeIORef  from  (  fromVal  -  summa  )  writeIORef  to  (  toVal  +  belopp  ) 

Detta orsakar problem i samtidiga situationer där flera överföringar kan ske på samma konto samtidigt . Om det fanns två överföringar som överför pengar från konto från , och båda samtalen att överföra körde linje (A) innan någon av dem hade skrivit sina nya värden, är det möjligt att pengar skulle sättas in på de andra två kontona, med endast ett av kontona. belopp som överförs tas bort från kontot från , vilket skapar ett tävlingsvillkor . Detta skulle lämna bankansökan i ett inkonsekvent tillstånd.

En traditionell lösning på ett sådant problem är låsning. Till exempel kan lås placeras runt modifieringar av ett konto för att säkerställa att krediter och debiteringar sker atomärt. I Haskell utförs låsning med MVars:

    

       
    
       
        

       
    
       
         typ  Konto  =  MVar  Heltalskredit  ::  Heltal  ->  Konto  -  >  IO  ()  kreditbelopp  konto  =  gör  nuvarande  <-  taMVar  konto  putMVar  konto  (  aktuell  +  belopp  )  debitering  ::  Heltal  ->  Konto  -  >  IO  (  )  debetbelopp  konto  =  gör  nuvarande  <-  taMVar  konto  putMVar  konto  (  aktuellt  -  belopp  ) 

Att använda sådana procedurer kommer att säkerställa att pengar aldrig kommer att gå förlorade eller vinnas på grund av felaktig interfoliering av läsningar och skrivningar till ett individuellt konto. Men om man försöker komponera dem tillsammans för att skapa en procedur som överföring:

         
     
      
       överför  ::  Heltal  ->  Konto  -  >  Konto  ->  IO  (  )  överför  belopp  från  till  =  gör  debiteringsbelopp  från  kreditbelopp  till 

ett tävlingsvillkor existerar fortfarande: det första kontot kan debiteras, sedan kan körningen av tråden avbrytas, vilket lämnar kontot som helhet i ett inkonsekvent tillstånd. Därför måste ytterligare lås läggas till för att säkerställa korrektheten av sammansatta operationer, och i värsta fall kan man behöva helt enkelt låsa alla konton oavsett hur många som används i en given operation.

Atomtransaktioner

För att undvika detta kan man använda STM-monaden, som gör att man kan skriva atomtransaktioner. Detta innebär att alla operationer inuti transaktionen är helt slutförda, utan att några andra trådar ändrar de variabler som vår transaktion använder, eller så misslyckas den, och tillståndet rullas tillbaka till där det var innan transaktionen påbörjades. Kort sagt, atomtransaktioner slutförs antingen helt eller så är det som om de aldrig genomfördes alls. Den låsbaserade koden ovan översätts på ett relativt enkelt sätt:

    

       
    
       
        

       
    
       
        

         
     
      
      typ  Konto  =  TVar  Heltal  kredit  ::  Heltal  ->  Konto  ->  STM  ()  kreditbelopp  konto  =  gör  nuvarande  <-  readTVar  konto  skrivTVar  konto  (  aktuell  +  belopp  )  debitering  ::  Heltal  ->  Konto  -  >  STM  (  )  debetbelopp  konto  =  gör  nuvarande  <-  readTVar  konto  skrivTVar  konto  (  aktuell  -  belopp  )  överföring  ::  Heltal  ->  Konto  -  >  Konto  ->  STM  ()  överför  belopp  från  till  =  gör  debitera  belopp  från  kreditbelopp  till  

Returtyperna för STM () kan användas för att indikera att vi komponerar skript för transaktioner. När det är dags att faktiskt utföra en sådan transaktion, används en funktion atomiskt :: STM a -> IO a . Ovanstående implementering kommer att se till att inga andra transaktioner stör variablerna den använder (från och till) medan den körs, vilket gör att utvecklaren kan vara säker på att tävlingsförhållanden som ovan inte uppstår. Fler förbättringar kan göras för att se till att någon annan " affärslogik " bibehålls i systemet, dvs att transaktionen inte ska försöka ta pengar från ett konto förrän det finns tillräckligt med pengar i det:

         
     
       
         0
         
                 
                 
          överför  ::  Heltal  ->  Konto  ->  Konto  -  >  STM  (  )  överför  belopp  från  till  =  gör  frånVal  <-  läsTVar  från  if  (  frånVal  -  belopp  )  >=  debitera  sedan  belopp  från  kreditbelopp  för att  annars  försöka igen 

Här har funktionen Försök igen använts, som återställer en transaktion och försöker igen. Att försöka igen i STM är smart eftersom det inte kommer att försöka köra transaktionen igen förrän en av variablerna den refererar till under transaktionen har modifierats av någon annan transaktionskod. Detta gör STM-monaden ganska effektiv.

Ett exempelprogram som använder överföringsfunktionen kan se ut så här:

  

  
 
  
  

    

  
       
       
              
      
             
             
                 
           
             
              

        
    
          

     
    

         
     
       
         0
         
                 
                 
         

       
    
       
        

       
    
       
         modul  Main  där  import  Control.Concurrent  (  forkIO  )  import  Control.Concurrent.STM  import  Control.Monad  (  för alltid  )  import  System.Exit  (  exitSuccess  )  typ  Konto  =  TVar  Heltal  main  =  do  bob  <-  newAccount  10000  jill  <-  newAccount  4000  repeatIO  2000  $  forkIO  $  atomically  $  överföra  1  bob  jill  för alltid  $  do  bobBalance  <-  atomically  $  readTVar  bob  jillBalance  <-  atomically  $  readTVar  jill  putStrLn  (  "Bobs balans: "  ++  visa  bobBalance  ++  ", Jills balans: "  ++  visa  jillBalance  )  om  bobBalance  ==  8000  sedan  exitSuccess  else  putStrLn  "Försöker igen."  repeatIO  ::  Heltal  ->  IO  a  -  >  IO  a  repeatIO  1  m  =  m  upprepaIO  n  m  =  m  >>  repeatIO  (  n  -  1  )  m  nyttKonto  ::  Heltal  -  >  IO  Konto  nyttKontobelopp  =  nyTVarIO  beloppsöverföring  ::  Heltal  ->  Konto  ->  Konto  ->  STM  (  )  överför  belopp  från  till  =  gör  frånVal  <  -  läsTVar  från  if  (  frånVal  -  belopp  )  >=  debitera  sedan  belopp  från  kreditbelopp  för att  annars  försöka  kreditera igen   ::  Heltal  ->  Konto  ->  STM  ()  kreditbelopp  konto  =  gör  aktuellt  <-  readTVar  konto  skrivTVar  konto  (  aktuellt  +  belopp  )  debet  ::  Heltal  ->  Konto  -  >  STM  ()  debetbelopp  konto  =  gör  nuvarande  <  -  readTVar  konto  skrivTVar  konto  (  aktuellt  -  belopp  ) 

som ska skriva ut "Bobs saldo: 8000, Jills saldo: 6000". Här har den atomära funktionen använts för att köra STM-aktioner i IO-monaden.

  1. ^ Simon Peyton Jones, Andrew D. Gordon och Sigbjörn Finne. Samtidigt Haskell . ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages ​​(PoPL). 1996. (Vissa avsnitt är inaktuella med avseende på den nuvarande implementeringen.)
  2. ^ Haskell Hierarchical Libraries , Control.Concurrent Arkiverad 2012-08-02 på archive.today
  3. ^ Tim Harris, Simon Marlow, Simon Peyton Jones och Maurice Herlihy . Komponerbara minnestransaktioner . ACM Symposium on Principles and Practice of Parallel Programming 2005 (PPoPP'05). 2005.
  4. ^ Control.Concurrent.STM