Hello everyone, it is generally assumed that Haskell is well-suited for concurrency. However, when doing concurrency tasks I found that it is actually not so easy.
In order to improve upon the situation, I am working on the new library multitasking
, which provides built-in functions for common concurrency patterns. Essentially, I want to make a concurrency toolbox. Currently included:
- Starting and waiting for tasks
- Racing
- Timeouts
- Worker threads
- Rate Limits
- Specialized concurrency variables
It implements structured concurrency based on the library ki
. Here is a racing implementation as a motivating example for structured concurrency:
Naive implementation
race = do
mvar <- newEmptyMVar
-- concurrently run two actions
thread1 <- forkIO $ action1 >>= putMVar mvar
thread2 <- forkIO $ action2 >>= putMVar mvar
-- wait for winner
result <- takeMVar mvar
-- kill the remaining one
killThread thread1
killThread thread2
useResult result
The above implementation should definitely not be used. It will only work as long as no exceptions are thrown. For example, both action1
and action2
might crash, resulting in an infinite waiting for the mvar
in the parent thread.
Structured concurrency implementation with multitasking
race = do
mvar <- newEmptyMVar
result <- multitask $ \coordinator -> do
-- scope starts
task1 <- start coordinator $ action1 >>= putMVar mvar
task2 <- start coordinator $ action2 >>= putMVar mvar
takeMVar mvar
-- scope ends
useResult result
The above implementation is safe while staying simple to understand. The function multitask
opens up a new scope for creating tasks, giving you a coordinator
. Tasks created with start coordinator
belong to that concurrency scope.
After the scope ends, all tasks belonging to that scope are killed. In other words, the lifetime of the created tasks is bounded by the scope. Thus, the slower task of the race is canceled automatically when the scope is exited.
Additionally, tasks propagate their exceptions to the parent thread. Therefore, an exception from action1
or action2
will interrupt takeMVar mvar
and stop the infinite wait. Of course, you still need to deal with the exception.
Feel free to try out the library. If you have ideas for new features, please share them here. I am still in the process of including additional functionality and multitasking
is not quite ready yet for a full package release.