Разветвленная функция чтения IORef, кажется, останавливает основной поток

Я проводил некоторые эксперименты с параллелизмом и видимостью памяти и столкнулся с этим странным поведением (см. встроенные комментарии):

module Main
    where

import Data.IORef
import Control.Concurrent
import System.CPUTime

import System.IO

main = do
    hSetBuffering stdout NoBuffering

    r <- newIORef False
    putStrLn "forking..."  -- PRINTED
    forkIO $ f r
    threadDelay 1000000

    putStrLn "writeIORef"  -- NEVER PRINTED
    writeIORef r True

    threadDelay maxBound

f :: IORef Bool -> IO ()
f r = readIORef r >>= \b-> if b then print "NEVER PRINTED" else f r

Я ожидал, что writeIORef не будет виден дочернему потоку, но не для того, чтобы основной поток просто (очевидно) остановился.

Скомпилировано на ghc 7.8.3

 cabal exec ghc -- --make -fforce-recomp -O2 -threaded visibility.hs  

и беги с

./visibility +RTS -N

Что тут происходит?

РЕДАКТИРОВАТЬ: Итак, моя машина имеет два реальных ядра и два ядра с гиперпоточностью, поэтому с +RTS -N GHC видит 4 возможности. Согласно ответу Габриэля Гонсалеса, я попробовал следующее, чтобы увидеть, может ли планировщик поместить оба потока на один и тот же физический процессор:

module Main
    where

import Data.IORef
import Control.Concurrent    
import GHC.Conc(threadCapability,myThreadId,forkOn)

main = do    
    r <- newIORef False
    putStrLn "going..."

    (cap,_) <- threadCapability =<< myThreadId
    forkOn (cap+1) $ f r                    -- TRIED cap+1, +2, +3....
    threadDelay 1000000

    putStrLn "writeIORef"                   -- BUT THIS STILL NEVER RUNS
    writeIORef r True

    threadDelay maxBound

f :: IORef Bool -> IO ()
f r = readIORef r >>= \b-> if b then print "A" else f r

person jberryman    schedule 31.08.2014    source источник


Ответы (2)


ghc приостанавливает потоки только в четко определенных безопасных точках, то есть только при выделении памяти. Я считаю, что ваш разветвленный поток никогда не выделяет память, поэтому он никогда не передает контроль другим потокам. Следовательно, ваш основной поток никогда не будет выполняться после того, как компилятор запланирует разветвленный поток (где-то в середине вашего threadDelay).

Вы можете узнать больше о безопасных точках здесь в разделе "Легкие потоки и параллелизм".

Редактировать: как упоминал Томас, вы можете использовать Control.Concurrent.yield, чтобы явно отказаться от управления, когда вы сталкиваетесь с такими ситуациями.

person Gabriel Gonzalez    schedule 01.09.2014
comment
Точно. Одним из решений является использование yield для разбиения блоков без распределения или других подобных точек. Таким образом, для занятого ожидания в вопросе у нас будет ... else yield >> f r. Очевидно, что циклы занятости, как правило, плохая идея. Одна альтернатива состоит в том, чтобы вместо этого использовать MVar и takeMVar для сигнализации. - person Thomas M. DuBuisson; 01.09.2014
comment
Извините, может быть, я туплю... зачем мне нужен разветвленный поток, чтобы передать управление обратно основному потоку, чтобы putStrLn "writeIORef" запустился? Я работаю с +RTS -N, скомпилирован с -threaded; разве два потока не должны работать... одновременно? - person jberryman; 01.09.2014
comment
@jberryman Попробуйте вставить yield в цикл вашего разветвленного потока - person Gabriel Gonzalez; 01.09.2014
comment
Да, я думаю, это сработает. Добавление putStrLn в цикл исправило поведение, когда я пытался это сделать, поэтому оно, безусловно, связано с узким циклом без аллокаций. Но я не понимаю, почему разветвленному потоку нужно приостанавливать работу основного потока. - person jberryman; 01.09.2014

Похоже, это древняя ошибка ghc #367.

person jberryman    schedule 02.09.2014