使用putStrLn将Haskell forkIO线程彼此叠加

The*_*ist 6 io concurrency multithreading haskell stdout

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

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"
Run Code Online (Sandbox Code Playgroud)

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

Very interesting string
Very interesting string
Very interesting string
Very interesting string
Run Code Online (Sandbox Code Playgroud)

其中一个位于那里:

Main Thread
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

文本每隔几行就会移动,但很明显Very interesting strings最终会相互叠加,因为不知何故,同时使用的线程putStrLn最终会在stdout上写到彼此之上.为什么会这样,以及如何(无需借助消息传递,时间安排或其他一些过于复杂和复杂的解决方案)可以克服它?

chi*_*chi 5

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

(我也不确定在诸如 UTF8 之类的多字节编码中是否可以保证以原子方式处理多字节字符。)

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

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

正如下面评论中所建议的,我们还可以简化上述异常安全,如下所示:

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

  • 实际上,只需使用`withMVar` (9认同)
  • 使用 `atomicPutStrLn = 括号_(takeMVar lock) (putMVar lock ())` 因为如果抛出异常这将死锁 (4认同)