Skip to content

`stToIO` allows ST computations to run in multiple threads

Summary

The existence of the safe stToIO function enables one to use ST computations in multiple threads. This forces the use of atomic operations in ST computations which can have a 4x performance impact.

Context at #22764 (comment 473050)

Steps to reproduce

We might think ST computations are single-threaded an thus do not need to use atomic operations, so we might write a programST function as follows:

programST :: STRef s Integer -> ST s ()
programST ref = do
  n <- readSTRef ref
  if n <= 0
    then pure ()
    else do
      unsafeIOToST yield
      writeSTRef ref $! n - 1
      programST ref

(The unsafeIOToST yield is only to make it more likely that weird concurrent interleavings occur)

But we can actually use this function in an unsafe way:

countdownST :: Integer -> IO Integer
countdownST n = do
  ref <- stToIO (newSTRef n)
  forkIO $ stToIO (programST ref)
  stToIO (programST ref)
  s <- stToIO (readSTRef ref)
  pure s

main :: IO ()
main = do
  putStrLn . show =<< countdownST 1000000

Compiling with ghc -O2 T.hs -threaded and running with ./T +RTS -N2 sometimes gives me unexpected results:

$ ./T +RTS -N2
129

$ ./T +RTS -N2
100
Click to expand full repro source code
import Control.Monad.ST
import Control.Monad.ST.Unsafe
import Control.Concurrent
import Data.STRef

programST :: STRef s Integer -> ST s ()
programST ref = do
  n <- readSTRef ref
  if n <= 0
    then pure ()
    else do
      unsafeIOToST yield
      writeSTRef ref $! n - 1
      programST ref

countdownST :: Integer -> IO Integer
countdownST n = do
  ref <- stToIO (newSTRef n)
  forkIO $ stToIO (programST ref)
  stToIO (programST ref)
  s <- stToIO (readSTRef ref)
  pure s

main :: IO ()
main = do
  putStrLn . show =<< countdownST 1000000

Question

The paper "Lazy Functional State Threads" mentions multiple times that ST computations are supposed to be single-threaded, however the possibility of using RealWorld makes it possible to run "entangled" ST computations in multiple threads at the same time.

Is this intended to be possible?

Are the costs of requiring atomic operations worth the utility of the stToIO function? Or should the stToIO function be removed?

Edited by Jaro Reinders
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information