Queue.Synchronized 是不是比使用 Lock() 更快?

Posted

技术标签:

【中文标题】Queue.Synchronized 是不是比使用 Lock() 更快?【英文标题】:is Queue.Synchronized faster than using a Lock()?Queue.Synchronized 是否比使用 Lock() 更快? 【发布时间】:2011-06-16 15:13:58 【问题描述】:

我有一个队列,入队操作将由一个线程执行,而出队操作将由另一个线程执行。不用说,我必须为它实现一些线程安全。

我首先尝试在每个入队/出队之前对队列使用锁定,因为它可以更好地控制锁定机制。它运作良好,但我好奇的头脑让我进行了更多测试。

然后我尝试使用 Queue.Synchronized 包装器保持其他所有内容相同。现在,我不确定它是否属实,但这种方法的性能似乎确实有点快。

您是否认为,两者之间的性能实际上存在一些差异,或者我只是在这里想象一些事情..? :)

【问题讨论】:

使用任何一种方法来开锁都非常快。性能损失来自锁的竞争程度。 【参考方案1】:

当请求Queue.Synchonized 时,你会得到一个SynchronizedQueue 作为回报,它在内部队列上对EnqueueDequeue 的调用很少使用lock。因此,性能应该与使用Queue 并使用自己的lock 管理锁定EnqueueDequeue 相同。

你确实在想象事物——它们应该是一样的。

更新

事实上,当使用SynchronizedQueue 时,您正在添加一个间接层,因为您必须通过包装器方法才能到达它所管理的内部队列。如果有的话,这应该会大大减慢速度,因为堆栈上有一个额外的帧需要为每次调用进行管理。上帝知道内联是否会取消这一点。无论如何 - 它是最小的

更新 2

我现在已经对此进行了基准测试,并且正如我之前的更新中所预测的那样:

“Queue.Synchronized”比“Queue+lock”慢

我进行了单线程测试,因为它们都使用相同的锁定技术(即lock),因此以“直线”测试纯开销似乎是合理的。

我的基准测试为 Release 构建产生了以下结果:

Iterations      :10,000,000

Queue+Lock      :539.14ms
Queue+Lock      :540.55ms
Queue+Lock      :539.46ms
Queue+Lock      :540.46ms
Queue+Lock      :539.75ms
SynchonizedQueue:578.67ms
SynchonizedQueue:585.04ms
SynchonizedQueue:580.22ms
SynchonizedQueue:578.35ms
SynchonizedQueue:578.57ms

使用以下代码:

private readonly object _syncObj = new object();

[Test]
public object measure_queue_locking_performance()

    const int TestIterations = 5;
    const int Iterations = (10 * 1000 * 1000);

    Action<string, Action> time = (name, test) =>
    
        for (int i = 0; i < TestIterations; i++)
        
            TimeSpan elapsed = TimeTest(test, Iterations);
            Console.WriteLine("0:1:F2ms", name, elapsed.TotalMilliseconds);
        
    ;

    object itemOut, itemIn = new object();
    Queue queue = new Queue();
    Queue syncQueue = Queue.Synchronized(queue);

    Action test1 = () =>
    
        lock (_syncObj) queue.Enqueue(itemIn);
        lock (_syncObj) itemOut = queue.Dequeue();
    ;

    Action test2 = () =>
    
        syncQueue.Enqueue(itemIn);
        itemOut = syncQueue.Dequeue();
    ;

    Console.WriteLine("Iterations:0:0,0\r\n", Iterations);
    time("Queue+Lock", test1);
    time("SynchonizedQueue", test2);

    return itemOut;


[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect")]
private static TimeSpan TimeTest(Action action, int iterations)

    Action gc = () =>
    
        GC.Collect();
        GC.WaitForFullGCComplete();
    ;

    Action empty = () =>  ;

    Stopwatch stopwatch1 = Stopwatch.StartNew();

    for (int j = 0; j < iterations; j++)
    
        empty();
    

    TimeSpan loopElapsed = stopwatch1.Elapsed;

    gc();
    action(); //JIT
    action(); //Optimize

    Stopwatch stopwatch2 = Stopwatch.StartNew();

    for (int j = 0; j < iterations; j++) action();

    gc();

    TimeSpan testElapsed = stopwatch2.Elapsed;

    return (testElapsed - loopElapsed);

【讨论】:

如果 OP 在释放锁方面不够积极,或者太急于获取锁,则不会。 @Jason 我假设 OP 在 EnqueueDequeue 调用周围使用了最低限度的锁定,但我明白你的意思。我在回答中已经澄清了一点。 感谢您投入时间和精力来获得此问题的权威答案。向你致敬。 上述测试中的值与使用 ConcurrentQueue 相比如何?【参考方案2】:

我们无法为您回答这个问题。只有您自己可以通过获取分析器并根据您的应用程序中的真实数据测试两种场景(Queue.SynchronizedLock)来自己回答这个问题。它甚至可能不是您的应用程序的瓶颈。

也就是说,您可能应该只使用ConcurrentQueue

【讨论】:

@Danish:查看Rx。对于 .NET 3.5,它具有 ConcurrentQueue&lt;T&gt; 的后向端口。 @Dan 那个版本的ConcurrentQueue&lt;T&gt; 实际上通常更快吗?我已经看过几次该建议,但没有实际的性能数据。有数字吗? @chibacity:您是在问它是否与 .NET 4.0 中的 ConcurrentQueue&lt;T&gt; 一样快,或者它是否比由 lock 语句保护的普通 Queue&lt;T&gt; 更快?在前一种情况下,我不知道。在后一种情况下,它取决于争用的级别(几乎没有争用,带锁的Queue&lt;T&gt; 更好;随着潜在争用的增加,并发版本越来越领先)。如果你愿意,我可能会在某个地方挖掘出一些数字。 @Dan 我的主要观点是我知道 Rx (3.5) 中有一个版本,但我猜这不是 .Net 4.0 中的 same 版本. @chibacity:嗯,这几乎不是一个权威的答案,但是在 Reflector 中快速查看这两种类型的反编译版本表明它们是相同的。值得一提的是,Rx 具有 很多 来自 .NET 4.0 的并行化相关功能,例如整个 System.Threading.Tasks 命名空间。【参考方案3】:
    Queue.Synchronize 包装一个新的同步队列,同时锁定 Queue.SyncRoot 提供了一个对象来访问队列同步方式,这样您就可以在使用线程同时使用入队和出队操作时确保队列中的线程安全。

【讨论】:

以上是关于Queue.Synchronized 是不是比使用 Lock() 更快?的主要内容,如果未能解决你的问题,请参考以下文章

使用 php array count() 是不是比使用 SQL 行数更快?

使用一次时,显示列表是不是比即时模式更好?

访问者模式是不是比受控使用 RTTI 更好?

Math.pow 是不是比使用临时分配的乘法更昂贵?

对于自动化帐户,使用 azure 资源图是不是比 powershell cmdlet 更快?

将 alloca() 用于可变长度数组是不是比在堆上使用向量更好?