在多线程程序中设计Haskell数据类型的选择

时间:2014-07-05 03:20:55

标签: haskell

在多线程服务器应用程序中,我使用类型Client来表示客户端。 Client的性质非常可变:客户端发送UDP心跳消息以便在服务器上注册,该消息还可能包含一些实时数据(想想传感器)。我需要跟踪许多事情,例如最后一次心跳的时间戳和源地址,实时数据等。结果是一个包含许多状态的相当大的结构。每个客户端都有一个客户端ID,我使用包含在HashMap中的MVar来存储客户端,因此查找非常简单快捷。

type ID        = ByteString
type ClientMap = MVar (HashMap ID Client)

ClientMap的“全局”值可供每个线程使用。它与许多其他全局值一起存储在ReaderT转换器中。

Client本身是一个很大的不可变结构,使用严格的字段来防止空间泄漏:

data Client  = Client
  {
    _c_id        :: !ID
  , _c_timestamp :: !POSIXTime
  , _c_addr      :: !SockAddr
  , _c_load      :: !Int
    ...
  }
makeLenses ''Client

根据Parallel and Concurrent Programming in Haskell,在Concurrent Haskell中的通用设计模式中使用可变包装器中的不可变数据结构。收到心跳消息后,处理该消息的线程将构造一个新的Client,锁定MVar的{​​{1}},将HashMap插入Client将新的HashMap放在HashMap中。代码基本上是:

MVar

这种方法运行良好,但随着客户数量的增长(我们现在有数万个客户端),出现了几个问题:

  1. 客户端经常发送心跳消息(大约每30秒一次),导致modifyMVar hashmap_mvar (\hm -> let c = Client id ... in return $! M.insert id c hm) 的访问争用。
  2. 程序的内存消耗似乎很高。我的理解是,频繁更新ClientMap中包含的大型不可变结构将使垃圾收集器非常繁忙。
  3. 现在,为了减少全局MVar的争用,我尝试为每个客户端在hashmap_mvar中包含Client的可变字段,例如:

    MVar

    这似乎降低了争用级别(因为现在我只需要更新每个data ClientState = ClientState { _c_timestamp :: !POSIXTime , _c_addr :: !SockAddr , _c_load :: !Int ... } makeLenses ''ClientState data Client = Client { c_id :: !ID , c_state :: MVar CameraState } 中的MVar,粒度更精细),但程序的内存占用率仍然很高。我也尝试过UNPACK的一些字段,但这没有帮助。

    有什么建议吗? STM会解决争用问题吗?我应该使用包含在Client中的不可变数据结构之外的可变数据结构吗?

    另见Updating a Big State Fast in Haskell


    编辑:

    正如Nikita Volkov指出的那样,在典型的基于TCP的服务器 - 客户端应用程序中,共享地图闻起来像是糟糕的设计。但是,在我的情况下,系统是基于UDP的,这意味着没有“连接”这样的东西。服务器使用单个线程从所有客户端接收UDP消息,解析它们并相应地执行动作,例如,更新客户端数据。另一个线程定期读取地图,检查心跳的时间戳,并删除那些在过去5分钟内没有发送心跳的人,比如说。好像共享地图是不可避免的?无论如何,我知道首先使用UDP是一个糟糕的设计选择,但我仍然想知道如何通过UDP改善我的情况。

1 个答案:

答案 0 :(得分:2)

首先,为什么你需要共享地图呢?你真的需要与任何东西分享客户的私人状态吗?如果不是(这是客户端 - 服务器应用程序的典型情况),那么您可以简单地在没有任何共享映射的情况下四处走动。

实际上,a "remotion" library包含所有客户端 - 服务器通信,只需使用自定义协议扩展服务即可创建服务。你应该看看。

其次,在某个实体的字段上使用多个MVar总是潜在的竞争条件错误。当您需要以原子方式更新多个内容时,您应该使用STM。我不确定您的应用中是否存在这种情况,但您应该知道这一点。

第三,

  

客户端经常发送心跳消息(大约每30秒一次),导致ClientMap的访问争用

似乎只是最近发布的"stm-containers" library Map的工作。有关库的介绍,请参阅this blog post。您将能够使用此功能返回不可变Client模型。