首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >BlockingCollection(T)性能

BlockingCollection(T)性能
EN

Stack Overflow用户
提问于 2010-06-14 18:14:32
回答 2查看 14.6K关注 0票数 30

在我的公司有一段时间,我们使用了一个本地开发的ObjectPool<T>实现,它提供了对其内容的阻塞访问。它非常简单:一个Queue<T>,一个要锁定的object,以及一个AutoResetEvent,当添加一个项目时,它可以向一个“借用”线程发出信号。

这类方法的实质是以下两种方法:

代码语言:javascript
复制
public T Borrow() {
    lock (_queueLock) {
        if (_queue.Count > 0)
            return _queue.Dequeue();
    }

    _objectAvailableEvent.WaitOne();

    return Borrow();
}

public void Return(T obj) {
    lock (_queueLock) {
        _queue.Enqueue(obj);
    }

    _objectAvailableEvent.Set();
}

我们一直在使用这个类和其他一些集合类,而不是System.Collections.Concurrent提供的类,因为我们使用的是.NET 3.5,而不是4.0。但是最近我们发现,由于我们使用的是反应性扩展,所以实际上我们可以使用Concurrent命名空间(在System.Threading.dll中)。

当然,我认为由于BlockingCollectionConcurrent命名空间中的核心类之一,它可能比我或我的队友所写的任何东西都能提供更好的性能。

因此,我尝试编写一个非常简单的新实现:

代码语言:javascript
复制
public T Borrow() {
    return _blockingCollection.Take();
}

public void Return(T obj) {
    _blockingCollection.Add(obj);
}

令我惊讶的是,根据一些简单的测试(从多线程借用/返回池几千次),我们最初的实现在性能方面明显优于。它们看起来都工作正常,只是我们最初的实现似乎要快得多。

我的问题是:

  1. 为什么会这样?这可能是因为BlockingCollection<T>提供了更大的灵活性(我理解它通过包装IProducerConsumerCollection来工作),这必然会带来性能开销吗?
  2. 这仅仅是对BlockingCollection<T>类的一个完全错误的使用吗?
  3. 如果这是BlockingCollection<T>的适当使用,我只是没有正确地使用吗?例如,Take/Add方法是否过于简单,并且有一种性能更好的方法来获得相同的功能?

除非有人在回答第三个问题时有一些洞察力,否则现在看来我们将坚持原来的实现。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2010-06-14 18:25:31

这里有几种潜在的可能性。

首先,反应性扩展中的BlockingCollection<T>是一个后端,与.NET 4的最终版本不完全相同。如果这个后端的性能与.NET 4 RTM不同,我也不会感到惊讶(尽管我还没有详细描述这个集合)。在.NET 4中,TPL的大部分性能要比在.NET 3.5backport中的性能要好。

尽管如此,如果您有一个生产者线程和一个使用者线程,那么我怀疑您的实现将优于BlockingCollection<T>。使用一个生产者和一个使用者,您的锁对总体性能的影响将更小,而重置事件是在使用者端等待的一种非常有效的方法。

然而,BlockingCollection<T>被设计成允许许多生产者线程很好地“排队”数据。这在您的实现中将不能很好地执行,因为锁定争用会很快就会出现问题。

话虽如此,我也想在此指出一个误解:

...it可能会提供比我或我的队友更好的性能。

这往往不是真的。框架集合类通常执行得很好,但通常不是给定场景中最具表现力的选项。话虽如此,他们往往表现得很好,同时又非常灵活和健壮。它们的规模往往很大。在特定场景中,“Home-书面”集合类的性能通常优于框架集合,但在特定设计方案之外的场景中使用时往往会出现问题。我怀疑这就是其中一种情况。

票数 31
EN

Stack Overflow用户

发布于 2015-03-26 00:39:33

我在BlockingCollection 4中尝试了ConurrentQueue/AutoResetEvent组合体(类似于OP的解决方案,但没有锁),而后者对于我的用例来说要快得多,所以我放弃了BlockingCollection。不幸的是,这几乎是一年前的事了,我找不到基准结果。

使用单独的AutoResetEvent并不会使事情变得更加复杂。事实上,我们甚至可以一劳永逸地把它抽象成一个BlockingCollectionSlim.

BlockingCollection在内部也依赖于ConcurrentQueue,但是有吗?还需要额外地使用超薄的信号量和取消标记,这会产生额外的特性,但即使不使用也要付出一定的代价。还应该注意的是,BlockingCollection没有与ConcurrentQueue结合,但也可以与IProducerConsumerCollection的其他实现者一起使用。

一个无限制的、非常简单的BlockingCollectionSlim实现:

代码语言:javascript
复制
class BlockingCollectionSlim<T>
{
    private readonly ConcurrentQueue<T> _queue = new ConcurrentQueue<T>();
    private readonly AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
    public void Add(T item)
    {
        _queue.Enqueue(item);
        _autoResetEvent.Set();
    }
    public bool TryPeek(out T result)
    {
        return _queue.TryPeek(out result);
    }
    public T Take()
    {
        T item;
        while (!_queue.TryDequeue(out item))
            _autoResetEvent.WaitOne();
        return item;
    }
    public bool TryTake(out T item, TimeSpan patience)
    {
        if (_queue.TryDequeue(out item))
            return true;
        var stopwatch = Stopwatch.StartNew();
        while (stopwatch.Elapsed < patience)
        {
            if (_queue.TryDequeue(out item))
                return true;
            var patienceLeft = (patience - stopwatch.Elapsed);
            if (patienceLeft <= TimeSpan.Zero)
                break;
            else if (patienceLeft < MinWait)
            // otherwise the while loop will degenerate into a busy loop,
            // for the last millisecond before patience runs out
                patienceLeft = MinWait;
            _autoResetEvent.WaitOne(patienceLeft);
        }
        return false;
    }
    private static readonly TimeSpan MinWait = TimeSpan.FromMilliseconds(1);
票数 14
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/3039724

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档