[Haskell-cafe] Software Transactional Memory and LWN
Neil Brown
nccb2 at kent.ac.uk
Thu Jun 11 08:01:45 EDT 2009
Ketil Malde wrote:
> So the naïve attempt at doing this would be something like:
>
> thread = do
> -- grab "lock 1"
> t <- readTVar lock
> when t retry
> writeTVar lock True
>
> -- grab "lock 2"
> t2 <- readTVar lock2
> when t2 retry writeTVar
> writeTVar lock2 True
>
> -- do something
> writeTVar lock2 False
> writeTVar lock False
>
> and another one with the locks reversed. But that won't work of
> course, since the 'retry' will rollback the taking of lock 1 as well.
> So do I need to split this up into separate STM transactions and
> orchestrate the locking from the IO monad?
>
Indeed:
type Lock = TVar Bool
claim :: Lock -> IO ()
claim tv = atomically $ do
b <- readTVar tv
when b retry
writeTVar tv True
release :: Lock -> IO ()
release tv = atomically $ writeTVar tv False
Writing a lock in STM is not useful for the purpose of doing some other
STM stuff inbetween anyway, because that goes against the point of STM
(you don't need locks for STM actions -- as you point out, the rollback
from the locks is not useful in your example). So it only makes sense
if you are doing some other IO action inbetween the claim and release.
For example, you might want to write to a socket from several threads,
and use locks to ensure exclusivity.
Often, using STM for locks is pretty silly because there is some other
way to do it (e.g. have the threads write their packets to an STM queue,
and have a single thread reading from the queue and sending on the
sockets) but they're a simple example of how you can create deadlock in
STM, e.g.
main = do
tv1 <- newTVarIO False
tv2 <- newTVarIO False
forkIO $ claim tv2 >> claim tv1 >> release tv1 >> release tv2
claim tv1 >> claim tv2 >> release tv2 >> release tv1
is a possible source of deadlock, or even the more straightforward:
main = newTVarIO True >>= claim
What is particularly cool about Haskell's STM implementation is that in
the second example (and possibly in the first one too) the run-time can
detect straight-out deadlock and will throw you a deadlock exception.
It does this via garbage collection -- if a thread is waiting on a TVar
that no other thread has a reference to, the thread is technically ripe
for garbage collection and the thread is instead woken up with an
exception. At least, I think that's the principle. I don't think it
can catch all cases (another thread may have the reference but may never
use it) but it's still quite impressive.
Thanks,
Neil.
More information about the Haskell-Cafe
mailing list