【问题标题】:Haskell forkIO threads writing on top of each other with putStrLnHaskell forkIO 线程使用 putStrLn 在彼此之上写入
【发布时间】:2015-11-09 12:12:21
【问题描述】:

我正在使用 Haskell 轻量级线程 (forkIO) 使用以下代码:

import Control.Concurrent

beginTest :: IO ()
beginTest = go
    where
    go = do
        putStrLn "Very interesting string"
        go
        return ()

main = do
    threadID1 <- forkIO $ beginTest
    threadID2 <- forkIO $ beginTest
    threadID3 <- forkIO $ beginTest
    threadID4 <- forkIO $ beginTest
    threadID5 <- forkIO $ beginTest

    let tID1 = show threadID1
    let tID2 = show threadID2
    let tID3 = show threadID3
    let tID4 = show threadID4
    let tID5 = show threadID5

    putStrLn "Main Thread"
    putStrLn $ tID1 ++ ", " ++ tID2 ++ ", " ++ tID3 ++ ", " ++ tID4 ++ ", " ++ tID5
    getLine
    putStrLn "Done"

现在预期的输出将是一大堆这些:

Very interesting string
Very interesting string
Very interesting string
Very interesting string

其中一个在某处:

Main Thread

然而,输出(或前几行)结果是这样的:

Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very interesting string
Very VVVViMeeeenarrrrtiyyyyen    r iiiieTnnnnshtttttreeeeierrrrnaeeeegdssss 
ttttsiiiitTnnnnrhggggir    nessssgatttt
drrrrIiiiiVdnnnne ggggr5



y1 ,VVVVi eeeenTrrrrthyyyyer    reiiiieannnnsdtttttIeeeeidrrrrn eeeeg5ssss 2tttts,iiiit nnnnrTggggih    nrssssgetttt
arrrrdiiiiVInnnnedggggr 



y5 3VVVVi,eeeen rrrrtTyyyyeh    rriiiieennnnsatttttdeeeeiIrrrrndeeeeg ssss 5tttts4iiiit,nnnnr ggggiT    nhssssgrtttt
errrraiiiiVdnnnneIggggrd



y  5VVVVi5eeeen
rrrrtyyyye    riiiiennnnsttttteeeeirrrrneeeegssss ttttsiiiitnnnnrggggi    nssssgtttt
rrrriiiiVnnnneggggr



y VVVVieeeenrrrrtyyyye    riiiiennnnsttttteeeeirrrrneeeegssss ttttsiiiitnnnnrggggi    nssssgtttt
rrrriiiiVnnnneggggr

文本每隔几行就会移动,尽管很明显Very interesting strings 最终会彼此重叠,因为不知何故,同时使用putStrLn 的线程最终会在每个线程的顶部写入 stdout其他。为什么会这样,以及如何(不借助消息传递、计时或其他一些过于复杂和令人费解的解决方案)来克服它?

【问题讨论】:

标签: multithreading haskell concurrency io stdout


【解决方案1】:

简单地说,putStrLn 不是原子操作。每个字符都可以与来自不同线程的任何其他字符交错。

(我也不确定在 UTF8 等多字节编码中是否可以保证原子处理多字节字符。)

如果你想要原子性,你可以使用共享互斥体,例如

do lock <- newMVar ()
   let atomicPutStrLn str = takeMVar lock >> putStrLn str >> putMVar lock ()
   forkIO $ forever (atomicPutStrLn "hello")
   forkIO $ forever (atomicPutStrLn "world")

正如下面的 cmets 所建议的,我们还可以将上面的异常安全简化如下:

do lock <- newMVar ()
   let atomicPutStrLn str = withMVar lock (\_ -> putStrLn str)
   forkIO $ forever (atomicPutStrLn "hello")
   forkIO $ forever (atomicPutStrLn "world")

【讨论】:

  • 使用atomicPutStrLn = bracket_ (takeMVar lock) (putMVar lock ()),因为如果抛出异常会死锁
  • 其实就用withMVar
【解决方案2】:

使用全局锁的版本。

import Control.Concurrent.MVar  (newMVar, takeMVar, putMVar, MVar)
import System.IO.Unsafe (unsafePerformIO)


{-# NOINLINE lock #-}
lock :: MVar ()
lock = unsafePerformIO $ newMVar ()

printer :: String -> IO ()
printer x= do
   () <- takeMVar lock
   let atomicPutStrLn str =  putStrLn str >> putMVar lock ()
   atomicPutStrLn x

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-02-23
    • 1970-01-01
    • 2023-03-14
    • 2013-05-28
    • 1970-01-01
    • 2021-09-24
    • 1970-01-01
    相关资源
    最近更新 更多