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

sda*_*das 3 multithreading haskell client-server

我正在尝试使用Haskell创建一个简单的服务器.当客户端连接到服务器时,服务器会记录其地址.每n微秒,服务器发送一个广播.

这是服务器

data Server = Server {
  sSocket :: Socket,
  sPort :: Port,
  sClients :: MVar [ClientAddress]
  }
Run Code Online (Sandbox Code Playgroud)

(注意允许从多个线程使用客户端的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
Run Code Online (Sandbox Code Playgroud)

服务器使用其线程并分叉.分叉线程处理任何传入的客户端请求

handleClientRequest server client = do
  clients <- takeMVar $ sClients server
  putMVar (sClients server) (client : clients)
Run Code Online (Sandbox Code Playgroud)

并使用该updateClients功能发送广播

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

我遇到的问题是"2"永远不会打印到屏幕上.我相信这是因为takeMVar线路updateClients永远不会完成.

为什么会冻结?

Joh*_*n L 5

你从一个空的MVar开始,所以takeMVar永远阻止.尝试使用newMVar []而不是newEmptyMVar,可能像这样:

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

现在,MVar总是满的,除非它实际上被客户端修改.

当您使用MVar来保护关键部分时,考虑正常状态是有帮助的; 在这种情况下,它是一个客户列表.MVar应该为空的唯一时间是客户端实际修改状态,因此如果您可以使用modifyMVar并设置初始状态,那么您的代码应该是无死锁的.

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