Fungsi pembaca IORef bercabang tampaknya menghentikan thread utama

Saya sedang melakukan beberapa eksperimen dengan konkurensi dan visibilitas memori dan mengalami perilaku aneh ini (lihat komentar di baris):

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

Saya berharap mungkin writeIORef tidak terlihat oleh utas anak, tetapi utas utama tidak (tampaknya) terhenti.

Dikompilasi pada ghc 7.8.3

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

dan jalankan dengan

./visibility +RTS -N

Apa yang sedang terjadi disini?

EDIT: Jadi mesin saya memiliki dua inti nyata dan dua inti hyperthreading, jadi dengan +RTS -N GHC melihat 4 kemampuan. Sesuai jawaban Gabriel Gonzalez, saya mencoba yang berikut ini untuk melihat apakah mungkin penjadwal meletakkan kedua utas pada prosesor fisik yang sama:

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 sumber


Jawaban (2)


ghc hanya menangguhkan thread pada titik aman yang terdefinisi dengan baik, yaitu hanya ketika memori dialokasikan. Saya yakin thread bercabang Anda tidak pernah mengalokasikan memori, sehingga tidak pernah melepaskan kendali ke thread lain. Oleh karena itu, thread utama Anda tidak pernah berkembang setelah kompiler menjadwalkan thread bercabang (suatu saat di tengah-tengah threadDelay Anda).

Anda dapat mempelajari lebih lanjut tentang titik aman di sini di bagian "Benang Ringan dan Paralelisme".

Sunting: Seperti yang disebutkan Thomas, Anda dapat menggunakan Control.Concurrent.yield untuk secara eksplisit melepaskan kendali ketika Anda menghadapi situasi seperti ini.

person Gabriel Gonzalez    schedule 01.09.2014
comment
Tepat. Salah satu solusinya adalah dengan menggunakan yield untuk memecah blok tanpa alokasi atau poin serupa lainnya. Jadi untuk yang sibuk menunggu di pertanyaan kita akan punya ... else yield >> f r. Jelas sekali putaran sibuk pada umumnya merupakan ide yang buruk. Salah satu alternatifnya adalah menggunakan MVar dan takeMVar untuk memberi sinyal. - person Thomas M. DuBuisson; 01.09.2014
comment
Maaf, mungkin saya terlalu padat... mengapa saya harus memerlukan thread bercabang untuk mengembalikan kontrol ke thread utama agar putStrLn "writeIORef" dapat berjalan? Saya menjalankan dengan +RTS -N, dikompilasi dengan -threaded; bukankah kedua utas itu harus berjalan... secara bersamaan? - person jberryman; 01.09.2014
comment
@jberryman Coba masukkan yield ke dalam loop thread bercabang Anda - person Gabriel Gonzalez; 01.09.2014
comment
Ya, menurutku itu akan berhasil. Menambahkan putStrLn di loop memperbaiki perilaku ketika saya mencobanya, jadi sepertinya itu terkait dengan loop ketat tanpa alokasi. Tapi saya tidak mengerti mengapa thread bercabang harus ditangguhkan agar thread utama dapat dilanjutkan - person jberryman; 01.09.2014

Sepertinya ini mungkin merupakan bug ghc #367 kuno.

person jberryman    schedule 02.09.2014