Haskell Language
совпадение
Поиск…
замечания
Хорошими ресурсами для изучения параллельного и параллельного программирования в Haskell являются:
Нерестные нити с `forkIO`
Haskell поддерживает множество форм параллелизма, и наиболее очевидным является разветвление потока с использованием forkIO
.
Функция forkIO :: IO () -> IO ThreadId
принимает действие IO
и возвращает свой ThreadId
, в то время как действие будет выполняться в фоновом режиме.
Мы можем продемонстрировать это довольно лаконично с помощью ghci
:
Prelude Control.Concurrent> forkIO $ (print . sum) [1..100000000]
ThreadId 290
Prelude Control.Concurrent> forkIO $ print "hi!"
"hi!"
-- some time later....
Prelude Control.Concurrent> 50000005000000
Оба действия будут выполняться в фоновом режиме, а второй почти гарантированно завершится до последнего!
Связь между потоками с помощью MVARD
Очень легко передавать информацию между потоками с MVar a
типа MVar a
и его сопутствующих функций в Control.Concurrent
:
-
newEmptyMVar :: IO (MVar a)
- создает новыйMVar a
-
newMVar :: a -> IO (MVar a)
- создает новыйMVar
с заданным значением -
takeMVar :: MVar a -> IO a
- извлекает значение из данногоMVar
или блокирует до тех пор, пока не будет доступно -
putMVar :: MVar a -> a -> IO ()
- помещает заданное значение вMVar
или блокирует до тех пор, пока оно не будет пустым
Давайте суммируем числа от 1 до 100 миллионов в потоке и ожидаем результата:
import Control.Concurrent
main = do
m <- newEmptyMVar
forkIO $ putMVar m $ sum [1..10000000]
print =<< takeMVar m -- takeMVar will block 'til m is non-empty!
Более сложная демонстрация может заключаться в том, чтобы принимать пользовательский ввод и суммирование в фоновом режиме, ожидая большего ввода:
main2 = loop
where
loop = do
m <- newEmptyMVar
n <- getLine
putStrLn "Calculating. Please wait"
-- In another thread, parse the user input and sum
forkIO $ putMVar m $ sum [1..(read n :: Int)]
-- In another thread, wait 'til the sum's complete then print it
forkIO $ print =<< takeMVar m
loop
Как было сказано ранее, если вы назовете takeMVar
и MVar
пуст, он блокируется до тех пор, пока другая нить не помещает что-то в MVar
, что может привести к проблеме обеденных MVar
. То же самое происходит и с putMVar
: если он заполнен, он блокирует «пока он не будет пустым!
Возьмите следующую функцию:
concurrent ma mb = do
a <- takeMVar ma
b <- takeMVar mb
putMVar ma a
putMVar mb b
Мы запускаем две функции с некоторыми MVar
s
concurrent ma mb -- new thread 1
concurrent mb ma -- new thread 2
Что может случиться, так это то, что:
- Тема 1 читает
ma
и блокируетma
- Thread 2 читает
mb
и, таким образом, блокируетmb
Теперь Thread 1 не может прочитать mb
поскольку Thread 2 заблокировал его, и Thread 2 не может прочитать ma
поскольку Thread 1 заблокировал его. Классический тупик!
Атомные блоки с программной транзакционной памятью
Еще одним мощным и зрелым инструментом параллелизма в Haskell является программная транзакционная память, которая позволяет нескольким потокам записывать в одну переменную типа TVar a
атомным способом.
TVar a
является основным типом, связанным с монадой STM
и выступает за транзакционную переменную. Они используются так же, как MVar
но в монаде STM
помощью следующих функций:
atomically :: STM a -> IO a
Выполнять серию действий STM атомарно.
readTVar :: TVar a -> STM a
Прочтите значение TVar
, например:
value <- readTVar t
writeTVar :: TVar a -> a -> STM ()
Напишите значение для данного TVar
.
t <- newTVar Nothing writeTVar t (Just "Hello")
Этот пример взят из Haskell Wiki:
import Control.Monad import Control.Concurrent import Control.Concurrent.STM main = do -- Initialise a new TVar shared <- atomically $ newTVar 0 -- Read the value before <- atomRead shared putStrLn $ "Before: " ++ show before forkIO $ 25 `timesDo` (dispVar shared >> milliSleep 20) forkIO $ 10 `timesDo` (appV ((+) 2) shared >> milliSleep 50) forkIO $ 20 `timesDo` (appV pred shared >> milliSleep 25) milliSleep 800 after <- atomRead shared putStrLn $ "After: " ++ show after where timesDo = replicateM_ milliSleep = threadDelay . (*) 1000 atomRead = atomically . readTVar dispVar x = atomRead x >>= print appV fn x = atomically $ readTVar x >>= writeTVar x . fn