Say we want to spin off threads every so often, but we also need the ability to wait until they all go away before exiting the application.
In Haskell, we can do this naive thing:
main = do
count <- newTVarIO 0
let spawnThread action = do
atomically $ modifyTVar' count (+1)
forkIO action
-- Now, in order to wait for all the threads to die, we can just slap some naive crap together
atomically $ do
value <- readTVar count
when (value > 0) retry
You are done. This code is correct and behaves just as well as a semaphore would.