为什么我的MVar会冻结我的代码?

3
我将尝试使用 Haskell 创建一个简单的服务器。当客户端连接到服务器时,服务器会记录其地址。每隔n微秒,服务器发送一次广播。
以下是服务器代码:
data Server = Server {
  sSocket :: Socket,
  sPort :: Port,
  sClients :: MVar [ClientAddress]
  }

注意MVar的使用,它允许客户端在多线程中使用。

这是创建服务器的方式

startServer port = withSocketsDo $ do
  socket  <- listenOn $ PortNumber $ fromIntegral port
  clients <- newEmptyMVar
  let server = Server socket port clients
  forkIO $ forever $ accept socket >>= forkIO . (handleClientRequest server)
  forever $ updateClients server 1000000

服务器使用其线程并派生出另一个线程。派生的线程处理任何传入的客户端请求。
handleClientRequest server client = do
  clients <- takeMVar $ sClients server
  putMVar (sClients server) (client : clients)

广播使用 updateClients 函数发送。

updateClients server frequency = do
  putStrLn "1"
  clients <- (takeMVar $ sClients server)
  putStrLn "2"
  putStrLn $ show $ length clients
  threadDelay frequency

我遇到的问题是屏幕上从未打印出“2”。我认为这是因为updateClients中的takeMVar行永远不会完成。为什么会冻结呢?

3
您需要使用newMVar []替代newEmptyMVar,因为您从空的MVar开始,所以takeMVar将永远阻塞。此外,如果可以使用withMVar而不是takeMVar/putMVar,那通常更好,因为如果出现异步异常,它会使MVar处于一致状态。 - John L
谢谢,John - 这完美地解决了问题。(我使用了 withMVar 和 modifyMVar_ 来替换 take 和 put)。如果你把这个作为答案提交,我会接受它。 - sdasdadas
1个回答

5

您需要使用与newEmptyMVar不同的newMVar []来开始空的MVar,这样takeMVar不会永远阻塞。以下是可能的示例:

startServer port = withSocketsDo $ do
  socket  <- listenOn $ PortNumber $ fromIntegral port
  clients <- newMVar []
  let server = Server socket port clients
  forkIO $ forever $ accept socket >>= forkIO . (handleClientRequest server)
  forever $ updateClients server 1000000

现在,MVar除了被客户端实际修改时,它总是处于满的状态。

当您使用MVar来保护关键部分时,考虑正常状态是很有帮助的;在这种情况下,它是客户端列表。仅有的情况是当客户端正在修改状态时,MVar应该为空,因此如果您可以使用modifyMVar并设置初始状态,则您的代码应该不会出现死锁。

此外,如果您可以使用withMVar而不是takeMVar/putMVar,则应该这样做,因为它在异步异常出现时使MVar状态保持一致。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接