简体   繁体   English

为什么我的并发Haskell程序过早终止?

[英]Why does my concurrent Haskell program terminate prematurely?

I have a UDP server that reflects every ping message it receives (this works well I think). 我有一台UDP服务器,可以反映它收到的每个ping消息(我认为这很好用)。 I the client side I would then like to do two things: 在客户端,我想做两件事:

  1. make sure that I fired off N (eg 10000) messages, and 确保我解雇了N(例如10000)封邮件,并且
  2. count the number of correctly received responses. 计算正确接收到的响应的数量。

It seems that either because of the nature of UDP or because of the forkIO thing, my client code below ends prematurely/does not do any counting at all. 似乎是由于UDP的性质还是因为forkIO的原因,下面的客户端代码过早地结束了/根本没有做任何计数。

Also I am very surprised to see that the function tryOnePing returns 250 times the Int 4. Why could this be? 我也很惊讶地看到函数tryOnePing返回Int 4的250倍。为什么会这样呢?

main = withSocketsDo $ do
        s <- socket AF_INET Datagram defaultProtocol
        hostAddr <- inet_addr host
        thread <- forkIO $ receiveMessages s
        -- is there any better way to eg to run that in parallel and make sure
        -- that sending/receiving are asynchronous? 


        -- forM_ [0 .. 10000] $ \i -> do
              -- sendTo s "ping" (SockAddrInet port hostAddr)
        -- actually this would be preferred since I can discard the Int 4 that
        -- it returns but forM or forM_ are out of scope here?

        let tryOnePing i = sendTo s "ping" (SockAddrInet port hostAddr)
        pings <- mapM tryOnePing [0 .. 1000]
        let c = length $ filter (\x -> x==4) pings

        -- killThread thread
        -- took that out to make sure the function receiveMessages does not
        -- end prematurely. still seems that it does

        sClose s
        print c
        -- return()

receiveMessages :: Socket -> IO ()
receiveMessages socket = forever $ do
        -- also tried here forM etc. instead of forever but no joy
        let recOnePing i = recv socket 1024
        msg <- mapM recOnePing [0 .. 1000]
        let r = length $ filter (\x -> x=="PING") msg
        print r
        print "END"

The main problem here is that when your main thread finishes, all other threads gets killed automatically. 这里的主要问题是,当您的主线程完成时,所有其他线程都会被自动杀死。 You have to get the main thread to wait for the receiveMessages thread , or it will in all likelyhood simply finish before any responses have been received. 您必须让主线程等待receiveMessages thread ,否则它很可能在收到任何响应之前就完成了。 One simple way of doing this is to use an MVar . 一种简单的方法是使用MVar

An MVar is a synchronized cell that can either be empty or hold exactly one value. MVar是一个同步单元,可以为空或仅保留一个值。 The current thread will block if it tries to take from an empty MVar or insert into a full one. 如果当前线程试图从空的MVar或插入完整的MVar则它将阻塞。 In this case, we don't care about the value itself, so we'll just store a () in it. 在这种情况下,我们不在乎值本身,因此我们将在其中存储一个()

We'll start with the MVar empty. 我们将从MVar空着开始。 Then the main thread will fork off the receiver thread, send all the packets, and try to take the value from the MVar . 然后,主线程将派生出接收器线程,发送所有数据包,并尝试从MVar获取值。

import Control.Concurrent.MVar

main = withSocketsDo $ do
    -- prepare socket, same as before

    done <- newEmptyMVar

    -- we need to pass the MVar to the receiver thread so that
    -- it can use it to signal us when it's done
    forkIO $ receiveMessages sock done

    -- send pings, same as before

    takeMVar done    -- blocks until receiver thread is done

In the receiver thread, we will receive all the messages and then put a () in the MVar to signal that we're done receiving. 在接收器线程中,我们将接收所有消息,然后将()放入MVar以表示已完成接收。

receiveMessages socket done = do
    -- receive messages, same as before

    putMVar done ()  -- allows the main thread to be unblocked

This solves the main issue, and the program runs fine on my Ubuntu laptop, but there are a couple more things you want to take care of. 这解决了主要问题,并且该程序可以在我的Ubuntu笔记本电脑上正常运行,但是还有其他一些要注意的事项。

  • sendTo does not guarantee that the whole string will be sent. sendTo不保证将发送整个字符串。 You'll have to check the return value to see how much was sent, and retry if not all of it was sent. 您必须检查返回值以查看发送了多少,如果没有全部发送就重试。 This can happen even for a short message like "ping" if the send buffer is full. 如果发送缓冲区已满,即使是像"ping"这样的短消息也可能发生这种情况。

  • recv requires a connected socket. recv需要连接的插座。 You'll want to use recvFrom instead. 您将要改用recvFrom (Although it still works on my PC for some unknown reason). (尽管由于某些未知原因它仍然可以在我的PC上运行)。

  • Printing to standard output is not synchronized, so you might want to alter this so that the MVar will be used to communicate the number of received packets instead of just () . 打印到标准输出是不同步的,因此您可能需要更改此设置,以便使用MVar来传达接收到的数据包的数量,而不仅仅是() That way, you can do all the output from the main thread. 这样,您可以完成主线程的所有输出。 Alternatively, use another MVar as a mutex to control access to standard output. 或者,使用另一个MVar作为互斥锁来控制对标准输出的访问。

Finally, I recommend reading the documentation of Network.Socket , Control.Concurrent and Control.Concurrent.MVar carefully. 最后,建议您仔细阅读Network.SocketControl.ConcurrentControl.Concurrent.MVar的文档。 Most of my answer is stitched together from information found there. 我的大部分答案都是从那里找到的信息中拼凑而成的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM