Haskell Concurrent.Channel: 这两个代码有什么区别?

Haskell Concurrent.Channel: What is difference between this two codes?

为什么这个代码

import Control.Monad
import Control.Concurrent
import Control.Concurrent.STM
import Control.Concurrent.STM.TChan

main = do
  ch <- newTChanIO
  forkIO $ consumer ch 0
  forkIO $ consumer ch 1
  ----------------------
  forkIO $ producer ch
  ----------------------
  return ()


producer ch = loop 0
  where
    loop n = do
      atomically $ writeTChan ch n
      threadDelay (10^5 :: Int)
      loop (n+1)


consumer ch n = forever $ do
  v <- atomically $ readTChan ch
  print (n, v)

的行为方式不同
import Control.Monad
import Control.Concurrent
import Control.Concurrent.STM
import Control.Concurrent.STM.TChan

main = do
  ch <- newTChanIO
  forkIO $ consumer ch 0
  forkIO $ consumer ch 1
  ----------------------
  producer ch
  ----------------------
  return ()


producer ch = loop 0
  where
    loop n = do
      atomically $ writeTChan ch n
      threadDelay (10^5 :: Int)
      loop (n+1)


consumer ch n = forever $ do
  v <- atomically $ readTChan ch
  print (n, v)

后面代码的输出是

(0,0)
(1,1)
(1,2)
(1,3)
(1,4)
(0,5)
(0,6)
(1,7)
...

但是,前者的输出只有

(0,0)

我打算 producer 无限期地向通道队列 ch 添加一个值,并且 consumer 无限期地从 ch.

中获取一个值

后面的代码和我的意图一样,但前者没有。

在事件日志 (ghc-events) 中,块发生在 producer 线程

中的 MVar 上
4775372: cap 0: stopping thread 8 (blocked on an MVar)

为什么之前的代码 (forkIO $ producer ch) 不会无限期地 运行.

http://hackage.haskell.org/package/base-4.10.0.0/docs/Control-Concurrent.html#g:12:

In a standalone GHC program, only the main thread is required to terminate in order for the process to terminate. Thus all other forked threads will simply terminate at the same time as the main thread (the terminology for this kind of behaviour is "daemonic threads").

If you want the program to wait for child threads to finish before exiting, you need to program this yourself.

http://chimera.labs.oreilly.com/books/1230000000929/ch07.html#sec_reminders:

[...] This tells us something important about how threads work in Haskell: the program terminates when main returns, even if there are other threads still running. The other threads simply stop running and cease to exist after main returns.