Поиск…


замечания

Хорошими ресурсами для изучения параллельного и параллельного программирования в 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. Тема 1 читает ma и блокирует ma
  2. 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


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow