2012-02-25 119 views
0

下面是一個示例程序的源:unsafePerformIO在線程應用程序無法正常工作

當我從ghci中運行它都PRINTJOB和printJob2運行正常,寫十行到一個文本文件中。

但是,當使用-threaded標誌編譯時,程序只寫入一行。

我有ArchLinux的

以下是編譯命令GHC 7.0.3:

ghc -threaded -Wall -O2 -rtsopts -with-rtsopts=-N -o testmvar testmvar.hs 

什麼我做錯了嗎?爲什麼它不能在線程模式下工作?

import Control.Concurrent.MVar 
import Control.Concurrent (forkIO) 
import Control.Exception (bracket) 
import Control.Monad (forM_) 
import System.IO.Unsafe (unsafePerformIO) 
import System.IO (hPutStrLn, stderr) 


{-# NOINLINE p #-} 
p :: MVar Int 
p = unsafePerformIO $ newMVar (1::Int) 


{-# NOINLINE printJob #-} 
printJob x = bracket (takeMVar p) (putMVar p . (+ 1)) 
        (\a -> do 
         appendFile "mvarlog.txt" $ "Input: " ++ x ++ "; Counter: " ++ show a ++ "\n" 
         ) 


{-# NOINLINE printJob2 #-} 
printJob2 = unsafePerformIO $ do 
    p2 <- newEmptyMVar 
    return $ (\x -> bracket (putMVar p2 True) (\_ -> takeMVar p2) 
        (\_ -> do 
         appendFile "mvarlog.txt" $ "preformed " ++ x ++ "\n" 
        )) 

main = do 
    forM_ [1..10] 
    (\x -> forkIO $ printJob (show x)) 

編輯:哈馬爾指出,如果主應用程序退出早於所有產生的線程,那麼他們將被殺死,並建議在主末尾添加的延遲。 我的確如他預測的那樣工作。

+3

我不確定這裏發生了什麼(編譯可能會觸發GHCi不存在的優化,並且這些優化消除了對'unsafePerformIO'的調用),但是我覺得值得再次說'顧名思義,unsafePerformIO'就是* unsafe *,如果你使用它,東西*將會中斷。 (好吧,除非你非常非常小心,但可能也是如此。) – 2012-02-26 00:07:42

+2

如果你在'main'的末尾添加延遲,你會得到相同的結果嗎?一旦主線程完成,所有其他線程都會被終止,所以根據事情的計劃方式,這可能什麼都不做 - 獨立於與'unsafePerformIO'相關的任何問題。 – hammar 2012-02-26 00:19:45

+0

@hammar你是對的!我在main的最後添加了threadDelay,現在一切正常。謝謝!如果你把它作爲一個單獨的答案,我會接受它。 – 2012-02-26 00:26:58

回答

5

問題是您的主線程結束得太快,當一個Haskell程序的主線程結束時,所有其他線程都會自動終止。根據線程如何安排,這可能發生在任何線程都有機會運行之前。

一個快速和骯髒的解決方案是簡單地在main末增加threadDelay,但一個更強大的方法是使用原始的像一個MVar信號同步,當它是確定主線程來完成。

例如:

main = do 
    vars <- forM [1..10] $ \x -> do 
    done <- newEmptyMVar -- Each thread gets an MVar to signal when it's done 
    forkIO $ printJob (show x) >> putMVar done() 
    return done 

    -- Wait for all threads to finish before exiting 
    mapM_ takeMVar vars 
+3

所有這一切說,使用'unsafePerformIO'在這裏幾乎肯定是一個壞主意。在這個用例中有很多方法。 – 2012-02-26 02:51:12

+0

@Louis我很樂意學習如果你讓我看到另一種方式來創建信號量而不會泄露抽象。最初我已經在全局結構中聲明瞭它,並在啓動應用程序服務器之前初始化它,然後將它作爲參數顯式傳遞給printJob函數。但從封裝的角度來看,這是錯誤的。 – 2012-02-27 18:11:50

-1

,當然這是行不通的。使用unsafePerformIO總是會困擾你。將您的代碼結構化爲不使用它。使用它來創建全局變量不是它的合法使用。這就是讀者monad的目的。 Haskell中的任何東西都不需要unsafePerformIO。

當人們推薦這個「詭計」時,它被殺死了。這就像盲人領導盲人一樣。只要不這樣做,你就不會有使用Haskell的問題。 Haskell爲每一個問題提供了非常美麗和優雅的解決方案,但是如果你堅持要與之對抗而不是學習它,那麼你總會遇到bug。

+4

對不起,但你是一個白癡。我正在使用它爲共享資源(打印機)創建信號量。除了使用全局MVar之外,沒有其他的方法可以做到。在這裏,你會發出關於「邪惡」UnsafePerformIO的廢話。這不是邪惡的。這只是一個先進的工具。顯然不適合喜歡你。 – 2012-02-27 02:22:27

+0

你是對的,它需要一個MVar,但你不正確,它需要unsafePerformIO。沒有什麼能夠阻止你在主函數體內初始化它並傳遞MVar引用。如果您不喜歡傳遞變量引用,請使用reader monad。如果你不喜歡在功能上構建你的代碼,那麼你爲什麼使用Haskell? – 2012-02-27 23:24:54

+0

誰說要求UnsafePerformIO?事實上,如果你這樣說,那麼甚至haskell本身並不是必需的,我可以用java編寫它。在函數之外初始化一個全局變量是醜陋的錯誤。你在這個地方灑滿了膽量。我的第一個實現是將這個MVar定義在一個模塊中,在另一個模塊中初始化,並在第三個中使用。這打破了所有封裝法則。而且我非常看重我的封裝更多關於UnsafePerformIO的愚蠢迷信。這個名字不安全真的意味着「遠離兒童」。工程師知道如何以及何時使用它。 – 2012-02-28 01:35:59