C# ConcurrentBag<;T>;和锁(List<;T>;)哪个添加或删除更快?

C# ConcurrentBag<;T>;和锁(List<;T>;)哪个添加或删除更快?,c#,.net,concurrency,C#,.net,Concurrency,我需要在列表上执行一些线程安全的操作。通常我只是简单地使用: lock(List<T>) { List<T>.Add(); List<T>.Remove(); } 锁(列表) { List.Add(); List.Remove(); } 我知道还有另一种方法,使用ConcurrentBag。但我不知道哪个更快,也不知道还有什么不同 编辑: 有些人只是建议我使用ConcurrentBag,因为这样更安全。但我担心这会让我的手术变慢 我有很多线程需

我需要在
列表上执行一些线程安全的操作。通常我只是简单地使用:

lock(List<T>)
{
   List<T>.Add();
   List<T>.Remove();
}
锁(列表)
{
List.Add();
List.Remove();
}
我知道还有另一种方法,使用
ConcurrentBag
。但我不知道哪个更快,也不知道还有什么不同

编辑:

有些人只是建议我使用
ConcurrentBag
,因为这样更安全。但我担心这会让我的手术变慢


我有很多线程需要从
列表中添加或删除对象,我想知道哪种方式更能提高性能。

不要使用
ConcurrentBag
替换锁定的
列表,除非您确定线程的访问模式,因为它使用隐藏的线程本地存储

谈论首选用法:

ConcurrentBag
是一种线程安全的包实现,针对同一线程同时生成和使用包中存储的数据的情况进行了优化。”

还需要注意的是,
List
是有序的,
ConcurrentBag
是无序的。如果您不关心集合中的顺序,我将使用
ConcurrentQueue

关于性能,下面是来自
ConcurrentBag
的一些代码。但要考虑的首要问题是,如果你做了<代码>取<代码>,你的线程本地存储是空的,它会从其他线程中窃取。 当它需要偷窃时,请注意它处于锁定状态。还请注意,它可以在一个
Take
上锁定多次,因为
TrySteal
可能会失败,并从
Steal
多次调用(未显示)

最后,即使添加也会导致锁被占用

private void AddInternal(ConcurrentBag<T>.ThreadLocalList list, T item)
{
    bool lockTaken = false;
    try
    {
        Interlocked.Exchange(ref list.m_currentOp, 1);
        if (list.Count < 2 || this.m_needSync)
        {
            list.m_currentOp = 0;
            Monitor.Enter((object) list, ref lockTaken);
        }
        list.Add(item, lockTaken);
    }
    finally
    {
        list.m_currentOp = 0;
        if (lockTaken)
            Monitor.Exit((object) list);
    }
}
private void AddInternal(ConcurrentBag.ThreadLocalList列表,T项)
{
bool-locktake=false;
尝试
{
联锁交换(参考列表m_currentOp,1);
if(list.Count<2 | | this.m|u needSync)
{
list.m_currentOp=0;
监控。输入((对象)列表,参考锁定);
}
列表。添加(项目,带锁);
}
最后
{
list.m_currentOp=0;
如果(已锁定)
监视器。退出((对象)列表);
}
}

列表
操作
添加
删除
为O(n),这意味着锁定的持续时间将取决于列表的大小。列表越大,并发性越低。但是,如果您总是在末尾添加和从末尾删除,那么实际上您有一个堆栈。在这种情况下,
add
remove
操作是O(1),您将拥有更短的锁


ConcurrentBag
实现为链表的链表(每个线程一个)。操作
add
take
为O(1)在一般情况下不需要锁。锁通常可以避免,这意味着它可能会更快。

您可以通过尝试轻松衡量不同方法的性能!这就是我刚刚得到的:

lock list: 2.162s ConcurrentBag: 7.264s 锁定列表:2.162s ConcurrentBag:7.264秒
使用系统;
使用System.Collections.Concurrent;
使用System.Collections.Generic;
使用系统诊断;
使用System.Linq;
使用System.Threading.Tasks;
公开课考试
{
public const int numotasks=4;
公共常数整数周期=1000*1000*4;
公共静态void Main()
{
var list=新列表();
var bag=新的ConcurrentBag();
配置文件(“锁定列表”,()=>{lock(list)list.Add(1);});
配置文件(“ConcurrentBag”,()=>bag.Add(1));
}
公共静态空心轮廓(字符串标签、动作工作)
{
var s=新秒表();
s、 Start();
列表任务=新列表();
对于(int i=0;i
{
对于(int j=0;j
必须链接到-请确保使用测量值/具体要求更新您的问题。注意,它是专门创建要锁定的对象,而不是锁定共享对象或类型(您锁定的对象不明显,因为
锁定(列表)
甚至不会编译)。为什么不试试
ConcurrentStack
ConcurrentQueue
?@Gabe,我认为它是有序的,速度更慢。想解释一下否决票吗?
ConcurrentBag
不是锁定列表的合适替代品。为什么使用TLS是一个问题?@Gabe请阅读更多关于为什么TLS是一个问题的信息。要点是如果从包中取出的同一线程不是插入包中的同一线程。这会带来很大的开销。更一般的建议可能是:除非你知道自己在做什么,否则不要尝试多线程代码。不完全了解幕后情况的人不太可能偶然发现一个提高性能的解决方案。我看到了
ConcurrentBag
是如何针对相同的线程场景进行优化的,但我没有看到任何迹象表明非相同的线程使用会很慢,或者为什么TLS的使用会成为一个问题。这不是
列表。添加
O(1)?@DaveZych:是的,但只在每个
add
上摊销。任何给定的
add
可能需要一个操作,也可能需要N个操作。如果您连续调用
add
1048576次,最后一次调用将不得不执行1048576次操作。@DaveZych,这只是在末尾添加。在其他任何地方添加都需要移动每个操作em在列表中的后面,使其成为O(n),即使它不需要分配新的支持数组。@Servy right,但是
private void AddInternal(ConcurrentBag<T>.ThreadLocalList list, T item)
{
    bool lockTaken = false;
    try
    {
        Interlocked.Exchange(ref list.m_currentOp, 1);
        if (list.Count < 2 || this.m_needSync)
        {
            list.m_currentOp = 0;
            Monitor.Enter((object) list, ref lockTaken);
        }
        list.Add(item, lockTaken);
    }
    finally
    {
        list.m_currentOp = 0;
        if (lockTaken)
            Monitor.Exit((object) list);
    }
}
lock list: 2.162s ConcurrentBag: 7.264s
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

public class Test
{
    public const int NumOfTasks = 4;
    public const int Cycles = 1000 * 1000 * 4;

    public static void Main()
    {
        var list = new List<int>();
        var bag = new ConcurrentBag<int>();

        Profile("lock list", () => { lock (list) list.Add(1); });
        Profile("ConcurrentBag", () => bag.Add(1));
    }

    public static void Profile(string label, Action work)
    {
        var s = new Stopwatch();
        s.Start();

        List<Task> tasks = new List<Task>();

        for (int i = 0; i < NumOfTasks; ++i)
        {
            tasks.Add(Task.Factory.StartNew(() =>
            {
                for (int j = 0; j < Cycles; ++j)
                {
                    work();
                }
            }));
        }

        Task.WaitAll(tasks.ToArray());

        Console.WriteLine(string.Format("{0}: {1:F3}s", label, s.Elapsed.TotalSeconds));
    }
}