数据结构求助 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
不要在回答技术问题时复制粘贴 AI 生成的内容
wdc63
V2EX    程序员

数据结构求助

  •  
  •   wdc63 2022-10-08 14:45:53 +08:00 2948 次点击
    这是一个创建于 1099 天前的主题,其中的信息可能已经有所发展或是发生改变。

    当前项目有实现以下数据结构的需求: 1 大量的并行读取与修改; 2 支持在多线程情况下以 O1 或者极小的开销随机获取一个数据并删除(数据集为空时返回 null ,不报错。),插入一个数据。 3 基本上只有 2 一个需求,不需要顺序列表,不需要下标获取数据,不需要队列,不需要迭代器,无重复数据;

    使用语言为 C#,目前测试使用 lock(List<T>)或者 Hashset 的开销都太大,使用 ConcurrentBag 、ConcurrentDictionary 都无法满足要求,请问各位大佬实现上述需求最佳的数据结构是什么。

    26 条回复    2022-10-09 18:43:47 +08:00
    lmshl
        1
    lmshl  
       2022-10-08 14:55:21 +08:00
    并行读取从来都不是问题,并行修改在哪里都是问题

    建议重新思考读写模型,99%场景下并行修改都是可以避免的。比如把修改请求发送给数据结构持有者,Task<?> 接收修改结果,读取任意并发,写入排队。
    wdc63
        2
    wdc63  
    OP
       2022-10-08 15:02:31 +08:00
    @lmshl 不能提交延迟修改。业务需求大致上是这样的:

    数据集里的对象先称为服务,外部对象先称为代理。有上 W 个并行的代理向数据集请求可用的服务,随机提供一个给它,这个服务就被代理占用,然后这个服务就要从数据集移出,移入到正在接受服务的数据池,当服务完成后,服务可用,又从那边移出移入这边的可用服务数据池。如果提交延迟修改,就会发生多个代理被分配到同一个服务的错误。
    lmshl
        3
    lmshl  
       2022-10-08 15:06:32 +08:00   1
    其实是可以做到的,你还没想明白。

    看你描述完了整个流程以后,这不就是资源池么?要不你去看看(数据库,HTTP 链接池)资源池是怎么保证独占的?
    wdc63
        4
    wdc63  
    OP
       2022-10-08 15:08:41 +08:00
    @lmshl 确实没想明白,大佬能不能提示一下。
    optional
        5
    optional  
       2022-10-08 15:11:35 +08:00 via iPhone
    强一致性只能加锁,如果数据范围可以分区,尝试分区加锁
    lmshl
        6
    lmshl  
       2022-10-08 15:27:45 +08:00
    简单来说,把普通 Dictionary 给 Pin 到一个线程上,建一个接收命令的并发队列,持续消费它。

    设计两个函数:

    Task<TakeResult> Take(...) { 将指令和 TaskCompletionSource 写入队列,返回 Task }
    Task<SetResult> Set(...) { 将指令和 TaskCompletionSource 写入队列,返回 Task }

    单线程持续消费并发队列,消费成功后 TaskCompletionSorce::SetResult 告知调用者已经获取到



    但是实际上你需要占有的是背后的服务,而不是这个 Dictionary 数据结构,那你没必要用线程安全的数据结构,直接把 lock/semaphore 加在 Dict Value 上不好么?
    cxe2v
        7
    cxe2v  
       2022-10-08 15:42:49 +08:00
    还有你为何要维护两个集合,在一个集合里给占用了的做个标记不好吗
    wdc63
        8
    wdc63  
    OP
       2022-10-08 15:47:09 +08:00
    @lmshl 你的意思我懂了,我只需要 lock 被占用对象即可,如果这个对象被分配给另外一个请求者,可以先检查是否被 lock ,如果是就抛弃请求另一个。dict 多线程读取应该是安全的,但是没法获取随机元素。
    wdc63
        9
    wdc63  
    OP
       2022-10-08 15:48:18 +08:00
    @cxe2v 我要从未占用的对象中随机获取一个,一个集合在性能上办不到吧。
    leonshaw
        10
    leonshaw  
       2022-10-08 16:10:07 +08:00
    @wdc63 看不出来这里会成为瓶颈,感觉你说的一个锁 + List 够了。建议先做 profiling.
    documentzhangx66
        11
    documentzhangx66  
       2022-10-08 16:12:15 +08:00
    1.性能与资源粒度是个矛盾。只考虑性能的话,可以按 NUMA 把资源进行平均划分,甚至可以给每个 CPU Thread 安排一个专有资源池,这样就避免了 CPU 竞争内存的问题,接着甚至可以把这部分专有资源池直接优化到 L3 cache 里去。这种优化的本质是空间换时间,会存在一定程度的资源浪费,比如某些不怎么干活的 CPU Thread ,它的专有资源池可能就不怎么使用。

    2.如果存在避免不了竞争的资源区域,用 O1 的环形队列来代替 List 、Queue 等结构。这种优化的本质是极端的空间换时间,存在空间的大量浪费,但高端网络设备都这样搞。
    leonshaw
        12
    leonshaw  
       2022-10-08 16:16:22 +08:00
    #10 补充一下,删除的时候不要直接删,而是把最后一个换上来。
    visper
        13
    visper  
       2022-10-08 16:20:39 +08:00
    为什么要随机,像池的话通常不是拿个空闲能用的就行了嘛
    wdc63
        14
    wdc63  
    OP
       2022-10-08 16:44:45 +08:00
    @visper 不是一般的业务需求,是数理模型中的一部分,用服务池的概念只是一个举例,反正是必须要随机获取的,每个“服务”的属性都不一样。
    xuanbg
        15
    xuanbg  
       2022-10-08 17:41:14 +08:00
    你无论如何设计,他也只能是一个数组,无非就是要线程安全罢了。我记得 C#是有 ConcurrentBag 这么一个数据结构的,不明白为何不能满足 OP 的需求。
    fkdtz
        16
    fkdtz  
       2022-10-08 17:50:48 +08:00
    在共享字典的基础上,增加一个 pop 接口,返回一个元素并删除之。实现逻辑是随机删除,至于到底有多随机你可以慢慢调。

    不知道 ConcurrentDictionary 是哪里不满足要求了。
    liuhan907
        17
    liuhan907  
       2022-10-08 19:32:46 +08:00
    @wdc63
    如果你的使用过程不伴随异步的话,直接用 ThreadLocal 给每个线程分配一个池就好了。如果有异步。。。你都有异步了锁还不够用?
    wdc63
        18
    wdc63  
    OP
       2022-10-08 19:41:49 +08:00 via Android
    @fkdtz 并行读写是一部分,ConcurrentDictionary 主要是不能实现随机获取。
    liyunlong41
        19
    liyunlong41  
       2022-10-08 23:00:31 +08:00 via iPhone
    应该就是实现一个连接池吧,经典的实现方式就是 idle 和 busy 队列两个队列。另外为什么要随机呢,每次从 idle 队列头部 pop 一个连接放到 busy 就行,busy 用完了放到 idle 尾部排队,感觉也是比较公平的呀。
    wdc63
        20
    wdc63  
    OP
       2022-10-08 23:43:45 +08:00
    @liyunlong41 数学模型要求实现随机获取,不是公平问题,这个项目是一个仿真项目,不是面向用户的真实业务项目。
    helloworld000
        21
    helloworld000  
       2022-10-09 06:43:57 +08:00
    你有多大的 qps ? peak 的 read and write 分别是多少?脱离实际的需求都是瞎扯淡,你说的这些跟数据结构没半毛钱关系,完全是没想清楚问题在 yy

    tldr 直接上个 redis 一把梭就行了
    netnr
        22
    netnr  
       2022-10-09 08:36:44 +08:00 via Android
    ConcurrentDictionary 获取所有 key 为数组再随机取一个,或者其它方式单独存储一份 key
    wdc63
        23
    wdc63  
    OP
       2022-10-09 09:17:55 +08:00
    @helloworld000 高峰时,每个主 loop 有上千这样的查询、删除和写入等,每秒 60-100fps 。
    wdc63
        24
    wdc63  
    OP
       2022-10-09 09:18:19 +08:00
    @netnr 那每次查询都是 On ,太慢了。
    masterjoess
        25
    masterjoess  
       2022-10-09 10:29:34 +08:00
    一个 fixed size array 存放所有对象
    一个 atomic random queue 存放所有可用 index

    标记软删除或者另外线程回收
    这样可以吗?
    helloworld000
        26
    helloworld000  
       2022-10-09 18:43:47 +08:00
    @wdc63 就这点需求,redis 的 hash 就足够了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2646 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 06:36 PVG 14:36 LAX 23:36 JFK 02:36
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86