In Haskell, un approccio possibile è utilizzare la memoria transazionale del software per questo.
-- silly example mixing IO and STM
doOperation :: TVar Int -> IO ()
doOperation x = do -- IO monad
putStrLn "Incrementing value!"
atomically $ do -- STM monad
v <- readTVar x
-- putStrLn "We can't do I/O here, in the middle of STM!"
-- print v
writeTVar x $! v+1
putStrLn "Incrementing complete."
Usando atomically
, possiamo eseguire azioni STM nel mezzo delle azioni IO. Questo ci consente di leggere e scrivere x
, quindi di incrementarlo. In alternativa, modifyTVar' (+1)
farebbe anche l'incremento più convenientemente, ma soprattutto volevo separare la lettura dalla scrittura.
Per confronto, non c'è modo di eseguire azioni IO nel mezzo della transazione atomica STM. Se proviamo a decommentare le azioni IO nel codice sopra, il compilatore genera un errore di tipo perché IO
non è STM
. Questo è importante, dal momento che non dobbiamo stampare v
nel mezzo di una transazione che potrebbe ancora essere ripristinata - non possiamo annullare print v
, o in generale altri calcoli di I / O che potrebbero anche usare il disco o il Rete. (Gli sviluppatori di GHC usano launchMissiles
come esempio archetipo di IO con effetti collaterali internazionali, senza "annulla" possibile.)
Operativamente, l'implementazione del monad STM tiene traccia delle modifiche alla variabile x
. Durante la lettura, registra il vecchio valore di x
(nessun blocco in questo momento).
Durante la scrittura, anche il nuovo valore viene registrato, ma non scritto (nessun blocco).
Letture successive all'interno della transazione leggono il nuovo valore di x
(nessun blocco).
Alla fine della transazione, entriamo nella fase di commit: blocchiamo tutte le variabili coinvolte (usando un dead lock free ordering). Quindi testiamo se contengono ancora i vecchi valori. In caso contrario, eseguiamo il rollback della transazione e riproviamo. Se lo fanno, scriviamo i nuovi valori (dal log), sblocchiamo le variabili e abbiamo finito.
STM non è efficiente quanto gli algoritmi personalizzati per l'esclusione reciproca / l'esclusione dal deadlock che possono essere utilizzati per problemi specifici. È tuttavia molto generale e, a differenza delle serrature di base, molto componibile e flessibile.
Pure FP rende l'approccio più sicuro controllando che il programmatore utilizzi solo operazioni sicure nelle transazioni (senza IO).