ดูเหมือนว่าฟังก์ชันตัวอ่าน 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 อย่าง ตามคำตอบของ Gabriel Gonzalez ฉันลองทำสิ่งต่อไปนี้เพื่อดูว่าตัวกำหนดตารางเวลาอาจวางทั้งสองเธรดบนโปรเซสเซอร์ฟิสิคัลเดียวกันหรือไม่:

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 ของคุณ)

คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับจุดปลอดภัยได้ที่นี่ ในหัวข้อ "Lightweight Threads and Parallelism"

แก้ไข: ดังที่ Thomas กล่าวไว้ คุณสามารถใช้ 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