2012-02-25 120 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