在我的公司有一段时间,我们使用了一个本地开发的ObjectPool<T>实现,它提供了对其内容的阻塞访问。它非常简单:一个Queue<T>,一个要锁定的object,以及一个AutoResetEvent,当添加一个项目时,它可以向一个“借用”线程发出信号。
这类方法的实质是以下两种方法:
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中)。
当然,我认为由于BlockingCollection是Concurrent命名空间中的核心类之一,它可能比我或我的队友所写的任何东西都能提供更好的性能。
因此,我尝试编写一个非常简单的新实现:
public T Borrow() {
return _blockingCollection.Take();
}
public void Return(T obj) {
_blockingCollection.Add(obj);
}令我惊讶的是,根据一些简单的测试(从多线程借用/返回池几千次),我们最初的实现在性能方面明显优于。它们看起来都工作正常,只是我们最初的实现似乎要快得多。
我的问题是:
BlockingCollection<T>提供了更大的灵活性(我理解它通过包装IProducerConsumerCollection来工作),这必然会带来性能开销吗?BlockingCollection<T>类的一个完全错误的使用吗?BlockingCollection<T>的适当使用,我只是没有正确地使用吗?例如,Take/Add方法是否过于简单,并且有一种性能更好的方法来获得相同的功能?除非有人在回答第三个问题时有一些洞察力,否则现在看来我们将坚持原来的实现。
发布于 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-书面”集合类的性能通常优于框架集合,但在特定设计方案之外的场景中使用时往往会出现问题。我怀疑这就是其中一种情况。
发布于 2015-03-26 00:39:33
我在BlockingCollection 4中尝试了ConurrentQueue/AutoResetEvent组合体(类似于OP的解决方案,但没有锁),而后者对于我的用例来说要快得多,所以我放弃了BlockingCollection。不幸的是,这几乎是一年前的事了,我找不到基准结果。
使用单独的AutoResetEvent并不会使事情变得更加复杂。事实上,我们甚至可以一劳永逸地把它抽象成一个BlockingCollectionSlim.
BlockingCollection在内部也依赖于ConcurrentQueue,但是有吗?还需要额外地使用超薄的信号量和取消标记,这会产生额外的特性,但即使不使用也要付出一定的代价。还应该注意的是,BlockingCollection没有与ConcurrentQueue结合,但也可以与IProducerConsumerCollection的其他实现者一起使用。
一个无限制的、非常简单的BlockingCollectionSlim实现:
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);https://stackoverflow.com/questions/3039724
复制相似问题