3

I got stuck in understanding the concept of atomically in STM.

I illustrate with an example

import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad
import qualified Data.Map as Map 

main :: IO ()
main =  do
    d <- atomically$ newTVar Map.empty
    sockHandler  d 

sockHandler ::  TVar (Map.Map String Int)-> IO ()
sockHandler  d = do
    forkIO $ commandProcessor  d 1
    forkIO $ commandProcessor  d 2
    forkIO $ commandProcessor  d 3
    forkIO $ commandProcessor  d 4
    forkIO (threadDelay 1000 >> putStrLn "Hello World?")

    threadDelay 10000
    return ()

commandProcessor ::  TVar (Map.Map String Int)-> Int-> IO ()
commandProcessor  d i= do
  addCommand d i
  commandProcessor  d i 

addCommand  ::  TVar (Map.Map String Int) ->Int -> IO ()
addCommand    d i = do
  succ <- atomically $ runAdd d
  putStrLn  $"Result of add in " ++ (show i)++ " " ++( show succ)

runAdd  d =do
  dl <- readTVar d
  let (succ,g)= if   Map.member "a" dl
                  then
                      (False,dl)
                  else
                      (True,Map.insert "a" 9 dl)
  writeTVar d g
  return succ

The sample output would be like this:

Result of add in 1 True Result of add in 4 False Result of add in 1 FalseResult of add in 2 FalseResult of add in 3 False Hello World? Result of add in 4 False

Result of add in 1 FalseResult of add in 2 False Result of add in 3 False Result of add in 4 False

Result of add in 1 False Result of add in 2 FalseResult of add in 3 FalseResult of add in 4 False

Result of add in 1 False Result of add in 2 FalseResult of add in 3 FalseResult of add in 4 False

Result of add in 1 False Result of add in 2 FalseResult of add in 4 FalseResult of add in 3 False

Result of add in 1 False Result of add in 4 FalseResult of add in 2 FalseResult of add in 3 False

Result of add in 1 FalseResult of add in 4 False Result of add in 2 False Result of add in 3 False

Result of add in 1 FalseResult of add in 4 False

Result of add in 2 FalseResult of add in 3 False

Result of add in 1 FalseResult of add in 4 False

Result of add in 2 FalseResult of add in 3 False Result of add in 1 False Result of add in 4 False

Result of add in 2 FalseResult of add in 3 False

Result of add in 1 FalseResult of add in 4 False

When I read about atomically

. This means that all operations inside the transaction fully complete, without any other threads modifying the variables that our transaction is using, or it fails, and the state is rolled back to where it was before the transaction was begun. In short, atomic transactions either complete fully, or it is as if they were never run at all.

So to the question could the "return" of succ in some cases never happen? That is could the line succ <- atomically $ runAdd d putStrLn $"Result of add in " ++ (show i)++ " " ++( show succ)

give an output of "Result of add in ?i " ("as if they were never run at all")

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
Jonke
  • 6,525
  • 2
  • 25
  • 40

2 Answers2

7

If a transaction does get rolled back, what happens is that your program tries again. You can imagine the implementation of atomically to be something like this:

atomically action = do varState <- getStateOfTVars
                       (newState, ret) <- runTransactionWith action varState
                       success <- attemptToCommitChangesToTVars newState
                       if success
                         then return ret
                         else atomically action -- try again

In your case, the transaction will always run and will always complete. It may complete on the second or third try due to conflicts, but that's invisible to you, the user. STM makes sure that the action happens atomically, even if it takes a few goes before it's able to do that successfully.

Neil Brown
  • 3,558
  • 1
  • 27
  • 36
  • Nice, do you have any reference for this ? – Jonke Feb 02 '11 at 09:43
  • 1
    I find the original Haskell-STM paper very readable, you can get a copy here: http://research.microsoft.com/en-us/um/people/simonpj/papers/stm/stm.pdf Based on your example, I think just reading sections 3 and 6 would be enough for you, and should give you a good feel for how STM works. – Neil Brown Feb 02 '11 at 10:02
  • Note that if you need behavior other than retry-until-success, you can use functions from `Control.Monad.STM` to implement it. – John L Feb 02 '11 at 10:22
2
  1. threadDelay already returns (), no need to explicitly return () afterward
  2. newTVarIO is a concise version of atomically . newTVar.
  3. It's more readable if you use forever instead of tail calling yourself as done in commandProcessor.

As for your question, the answer is "yes". It's called live-lock in which your thread has work to do but it can't make progress. Imagine a really expensive function, expensive, and a really cheap function, cheap. If these operate in competing atomically blocks on the same TVar then the cheap function can cause the expensive function to never complete. I built an example for a related SO question.

Your closing example is not quite right though, if the STM operation never completes then putStrLn will never be reached and no output will be seen at all from that thread.

Community
  • 1
  • 1
Thomas M. DuBuisson
  • 64,245
  • 7
  • 109
  • 166
  • Technically starvation is the situation where a thread has work to do but cannot make progress. Livelock is the situation where none of the threads make progress (due to their activities repeatedly causing *all* of them to abort), and is exceedingly unlikely in STM systems with lazy acquire semantics. – Ben Feb 03 '11 at 03:45
  • @ben I'm accustom to defining `starvation` as when a thread can do work but **is never ran**, usually due to some aspect of scheduling. – Thomas M. DuBuisson Feb 03 '11 at 03:54
  • So, I could get a live-lock but the launchMissile (putStrnLn) will never fire. I was more afraid that I might get a live-lock and something eventually timed-out and left me in launch state with random value. – Jonke Feb 03 '11 at 10:30
  • @TomMD I prefer to separate starvation from livelock and deadlock. Starvation is when limited resources (access to a lock, a long enough uncontended window to complete a transaction, whatever) are distributed to threads inequitably such that some of them are unable to make progress, even though throughput of the system may be good. The key thing is that given a finite workload, a process with starvation will still eventually finish. – Ben Feb 22 '11 at 00:17
  • @TomMD OTOH Livelock, like deadlock, involves threads interfering with each other in such a way that none of them can make progress. While throughput *may* still be good if there are other non-locked threads making progress, the process will never finish, even with a finite workload. – Ben Feb 22 '11 at 00:19
  • @Jonke That's the basic guarantee of STM. If a transaction completes, it will behave **as if** it had run serially "in between" other transactions. You won't get states that other transactions create temporarily mid-execution. There are ways to let a transaction do something similar to "timing out" rather than keep trying forever, but then you know that it failed; it won't pretend to have succeeded with garbagey output. – Ben Feb 22 '11 at 00:28