C#的ConcurrentBag<T>如何实现线程安全集合?

畫卷琴夢
发布: 2025-08-08 10:42:02
原创
519人浏览过

concurrentbag通过线程局部存储和工作窃取实现线程安全,1. 每个线程优先操作自己的本地“小袋子”,add和take在本地无锁进行;2. 当本地为空时,线程从其他线程的袋子尾部窃取元素,减少冲突;3. 该机制在生产者-消费者同线程、任务无序处理、局部操作频繁的场景下性能最佳;4. 但存在工作窃取开销大、无序性、toarray/clear/contains性能差、内存开销高等局限;5. 与concurrentqueue(fifo)和concurrentstack(lifo)相比,concurrentbag不保证顺序,侧重吞吐量而非顺序一致性,适用于对顺序无要求但需高并发性能的负载均衡或任务池场景。

C#的ConcurrentBag<T>如何实现线程安全集合?

C#中的

ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
实现线程安全集合,其核心在于巧妙地结合了线程局部存储(Thread-Local Storage, TLS)和工作窃取(Work-Stealing)算法。这意味着,当一个线程添加或移除元素时,它会优先操作自己“专属”的局部存储空间,极大地减少了多线程之间的直接竞争,从而达到高效的线程安全。

解决方案

要理解

ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
如何做到线程安全,得深入它那有点“狡猾”的内部机制。它不像
ConcurrentQueue<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
ConcurrentStack<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
那样,通常围绕一个共享的、需要精细同步的数据结构打转。
ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
的聪明之处在于它试图避免这种直接竞争。

它为每个线程维护一个私有的、类似列表的“小袋子”(或者说,一个内部的、线程本地的双端队列Deque)。当一个线程调用

Add
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
方法时,它会将元素添加到自己线程的这个“小袋子”的头部。这个操作几乎是无锁的,因为每个线程都在操作自己的私有数据,互不干扰。这就像每个人都有自己的购物篮,往里面放东西的时候不用排队。

当一个线程需要通过

Take
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
方法取走一个元素时,它会首先尝试从自己的“小袋子”的头部取走。如果自己的袋子里有东西,那太好了,又是一个无锁操作。但如果自己的袋子空了,问题就来了:它需要找点活干。这时候,它就会尝试去“偷”其他线程袋子里的元素。这个“偷”的过程才是真正涉及到线程同步的地方。它会从其他线程的“小袋子”的尾部去取元素。之所以从尾部取,是为了减少与该线程自身在头部添加/移除时的冲突。这种窃取操作是需要加锁的,但因为是在本地袋子为空时才发生,所以整体上锁的频率和粒度都比直接共享的集合要低得多。

这种设计哲学,我个人觉得非常精妙,它利用了多线程行为的常见模式:线程通常会处理自己产生的数据。只有当一个线程“闲”下来,没有自己的活可干时,它才会去“打扰”别的线程。这种“各扫门前雪,有空再帮人”的策略,是

ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
实现高性能线程安全的关键。

ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
在什么场景下表现最佳?

从我的经验来看,

ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
在某些特定场景下能发挥出令人惊喜的性能优势,甚至超越其他并发集合。

一个非常典型的场景是生产者-消费者模式,尤其是当生产者和消费者是同一个线程,或者说,一个线程倾向于消费自己之前生产的元素时。举个例子,你有一个任务处理系统,每个工作线程会生成一些子任务,并且这些子任务最好由生成它们的线程来处理。如果这个线程处理完了自己的子任务,它才会去帮助其他线程处理它们的子任务。在这种情况下,

ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
的线程局部存储特性就显得尤为高效,因为大部分
Add
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
Take
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
操作都发生在线程内部,避免了跨线程的锁竞争。

另一个适合它的场景是任务分发与负载均衡。当你有大量不相关的任务需要并行处理,并且任务的顺序不重要时,

ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
可以作为一个任务池。每个工作线程从这个池中取出任务执行,如果自己的任务队列空了,就去“偷”其他线程的任务。这种设计非常适合那些任务量动态变化、且需要所有CPU核心都尽可能忙碌的计算密集型应用。

它也适用于高并发、但局部竞争较低的场景。如果你的操作模式是大量的

Add
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,并且
Take
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
操作相对较少,或者
Take
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
操作在大部分时间里都能从线程本地的“小袋子”里取到元素,那么
ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
的性能会非常出色。它将全局锁竞争转化为了局部的、偶尔的竞争,显著提升了吞吐量。

ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
的性能陷阱和局限性是什么?

尽管

ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
设计巧妙,但它并非银弹,使用不当同样会带来性能问题,甚至可能不如其他并发集合。

首先,工作窃取机制的开销。虽然它旨在减少竞争,但如果你的应用模式导致频繁的工作窃取,比如所有线程都很快清空了自己的本地“小袋子”,然后同时去抢一个繁忙线程的元素,那么这种窃取操作的开销(包括锁竞争和跨线程内存访问)就会变得非常显著,甚至可能导致性能下降。我见过一些案例,当

Take
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
操作远多于
Add
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,且所有线程都在争抢少数几个“富裕”线程的资源时,
ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
的表现反而不如预期。

其次,无序性是一个重要的局限。

ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
不保证任何元素的取出顺序,它既不是FIFO(先进先出),也不是LIFO(后进先出)。你取出的元素很可能是你当前线程自己最后放入的(从本地袋子取),也可能是其他线程放入的某个元素(窃取而来)。如果你的业务逻辑对元素的处理顺序有严格要求,比如消息队列、事件日志等,那么
ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
是绝对不适合的,你可能需要考虑
ConcurrentQueue<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

再者,某些操作会破坏其性能优势。例如,

ToArray()
登录后复制
Clear()
登录后复制
Contains()
登录后复制
方法
。这些操作需要遍历所有线程的局部“小袋子”,并可能涉及全局锁或复杂的同步机制,因此它们的性能开销通常会非常大。如果你需要频繁地将集合内容转换为数组,或者频繁检查某个元素是否存在,那么
ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
的性能优势将荡然无存,甚至可能成为瓶颈。它更适合作为一个动态的、只关注添加和移除的“工作池”。

最后,内存开销也是一个潜在问题。由于每个线程都可能维护自己的内部存储,如果你的应用程序创建了大量短生命周期的线程,或者线程池中的线程数量非常多,并且每个线程都短暂地使用了

ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,那么这些分散的内部存储可能会占用更多的内存,并且增加垃圾回收的压力。

ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
ConcurrentQueue<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
ConcurrentStack<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
的主要区别是什么?

理解

ConcurrentBag<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
ConcurrentQueue<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
ConcurrentStack<T>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
之间的差异,是选择正确并发集合的关键。它们虽然都提供线程安全,但在内部机制、性能特点和适用场景上有着根本性的不同。

最核心的区别在于元素顺序的保证

  • ConcurrentQueue<T>
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    :严格遵循FIFO(First-In, First-Out)原则,即先进入集合的元素,总是先被取出。它就像一个排队的队伍,谁先来谁先走。这使得它非常适合实现消息队列、任务调度等需要保持严格处理顺序的场景。
  • ConcurrentStack<T>
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    :严格遵循LIFO(Last-In, First-Out)原则,即最后进入集合的元素,总是最先被取出。它就像一叠盘子,你总是从最上面取,也总是把新盘子放在最上面。这让它非常适合实现撤销/重做功能、调用堆栈等需要处理最近状态的场景。
  • ConcurrentBag<T>
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    不保证任何顺序。你取出的元素可能是你当前线程最后放入的,也可能是从其他线程“偷”来的某个元素。这种无序性是其实现高性能的关键,因为它不需要为了维护顺序而引入复杂的同步机制。

其次是内部实现和性能侧重

  • ConcurrentQueue<T>
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    ConcurrentStack<T>
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    通常基于共享的、链表或数组结构,通过复杂的无锁算法(如CAS操作)或细粒度锁来保证在多线程访问时的原子性和一致性,它们更侧重于在共享资源上的高效同步。
  • ConcurrentBag<T>
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    则如前所述,通过线程局部存储和工作窃取来减少对共享资源的直接竞争。它的设计哲学是“能不共享就不共享,实在要共享再同步”。这使得它在
    Add
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    操作和大部分
    Take
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    操作上拥有极低的同步开销,特别是在线程倾向于处理自己数据的场景。

最后,它们的适用场景也因此不同。

  • 如果你需要严格的顺序保证(FIFO或LIFO),并且集合中的元素是需要按特定顺序处理的任务或数据,那么
    ConcurrentQueue<T>
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    ConcurrentStack<T>
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    是你的首选。
  • 如果你对元素的处理顺序没有要求,但希望在多线程环境下最大化吞吐量,减少锁竞争,并且你的线程模式是倾向于处理自己生产的数据,或者能够通过“窃取”来平衡负载,那么
    ConcurrentBag<T>
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    会是更优的选择。它更像是一个“任务池”或“物品袋”,只关心有没有东西可取,不关心是哪个线程放的,也不关心是第几个放的。

以上就是C#的ConcurrentBag<T>如何实现线程安全集合?的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号