C#中ConcurrentBag和List加lock有什么区别

来源:苹果APP网作者:菲律宾程序员头衔:程序员
导读:本期聚焦于小伙伴创作的《C#中ConcurrentBag和List加lock有什么区别》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C#中ConcurrentBag和List加lock有什么区别》有用,将其分享出去将是对创作者最好的鼓励。

在C#多线程开发中,当多个线程需要同时操作集合时,保证集合的线程安全是核心需求。常见的实现方式有两种,一种是使用内置的线程安全集合ConcurrentBag,另一种是给普通的List加上lock锁来控制并发访问,这两种方式看似都能解决线程安全问题,实际存在诸多区别。

C#中ConcurrentBag和List加lock有什么区别

基本概念介绍

ConcurrentBag

ConcurrentBag是.NET Framework 4.0之后引入的线程安全集合,属于System.Collections.Concurrent命名空间,它是一个无序的对象集合,专门针对同一线程既生产又消费元素的场景做了优化,内部实现了无锁或细粒度锁的机制来保障线程安全。

List加lock

List是普通的泛型集合,本身不支持线程安全,当多个线程同时读写时会出现数据不一致的问题。给List加lock的思路是通过lock关键字对共享的List对象加锁,保证同一时间只有一个线程能访问集合,从而避免并发冲突。

核心区别对比

实现原理差异

ConcurrentBag内部采用了线程本地存储的机制,每个线程会维护自己的本地队列,当线程操作自己本地队列的元素时不需要加锁,只有在需要访问其他线程的队列或者本地队列元素不足时才会使用轻量级的锁进行同步,整体锁的粒度非常细。

而List加lock的方式,本质是用一个全局的锁对象(通常是List实例本身或者单独的object对象)来控制所有线程的访问,不管线程是读还是写操作,只要访问集合就需要获取锁,锁的粒度是全局的,并发度相对较低。

功能特性差异

ConcurrentBag提供了一些专门针对并发场景的方法,比如Add方法添加元素、TryTake方法尝试取出元素,这些方法内部已经处理了线程安全的逻辑,不需要开发者额外加锁。同时它支持枚举操作,但枚举过程中如果集合被修改,不会抛出异常,而是返回枚举开始时的集合快照。

给List加lock的方式,所有操作都需要手动包裹在lock代码块中,包括添加、删除、遍历等操作,如果遗漏了lock就可能出现线程安全问题。而且如果在lock块内部进行遍历,同时其他线程修改了List,会直接抛出InvalidOperationException异常。

性能表现差异

在单线程或者线程竞争不激烈的场景下,List加lock的性能可能略好,因为ConcurrentBag的内部实现有一定的额外开销。但是在多线程竞争激烈的场景下,尤其是每个线程频繁操作自己添加的元素时,ConcurrentBag的性能会远超List加lock,因为它的本地队列操作几乎无锁,减少了线程阻塞的概率。

我们可以通过一个简单的性能测试来直观对比两者的差异,测试场景是10个线程同时向集合中添加10000个元素:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        // 测试ConcurrentBag
        ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();
        Stopwatch sw1 = Stopwatch.StartNew();
        Parallel.For(0, 10, _ =>
        {
            for (int i = 0; i < 10000; i++)
            {
                concurrentBag.Add(i);
            }
        });
        sw1.Stop();
        Console.WriteLine($"ConcurrentBag添加10万个元素耗时:{sw1.ElapsedMilliseconds}毫秒");

        // 测试List加lock
        List<int> list = new List<int>();
        object lockObj = new object();
        Stopwatch sw2 = Stopwatch.StartNew();
        Parallel.For(0, 10, _ =>
        {
            for (int i = 0; i < 10000; i++)
            {
                lock (lockObj)
                {
                    list.Add(i);
                }
            }
        });
        sw2.Stop();
        Console.WriteLine($"List加lock添加10万个元素耗时:{sw2.ElapsedMilliseconds}毫秒");
    }
}

实际运行后可以看到,在高并发添加元素的场景下,ConcurrentBag的耗时通常只有List加lock的十分之一甚至更少,性能优势非常明显。

适用场景选择

如果你的场景是多个线程都需要往集合中添加元素,同时每个线程也主要消费自己添加的元素,或者不需要保证集合的严格顺序,那么优先选择ConcurrentBag,它的性能和线程安全特性都更适配这类场景。

如果你的场景是多线程操作集合但竞争不激烈,或者需要集合保持插入顺序,或者需要频繁对集合进行遍历、查找等操作,而ConcurrentBag的无序特性无法满足需求,那么可以选择List加lock的方式,此时只需要注意所有操作都加上锁即可。

注意事项

  • 使用ConcurrentBag时不需要额外加锁,再次加锁反而会破坏其性能优势,增加不必要的开销。
  • 使用List加lock时,锁对象不要使用值类型,也不要使用public的对象,避免外部代码意外修改锁状态导致同步失效。
  • ConcurrentBag的TryTake方法在集合为空时会返回false,不会阻塞线程,如果需要阻塞等待元素,需要自己实现等待逻辑。
需要注意的是,不管是ConcurrentBag还是List加lock,都不是万能的线程安全方案,实际开发中需要根据具体的业务场景、并发量、功能需求来选择最合适的实现方式。

ConcurrentBagListlock线程安全修改时间:2026-06-30 18:45:32

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。