【问题标题】:Concurrent Priority Queue in .NET 4.0.NET 4.0 中的并发优先级队列
【发布时间】:2011-04-30 07:30:04
【问题描述】:

.NET 4.0 中似乎有很多与并发相关的改进,这些改进可能依赖于并发优先级队列。框架内是否有体面的优先级队列实现可供重用?

【问题讨论】:

标签: c# .net concurrency


【解决方案1】:

在 msdn 上,作为“使用 .NET Framework 进行并行编程的示例”的一部分,有一个实现。见ParallelExtensionsExtras

文件ConcurrentPriorityQueue.cs源代码的直接链接

【讨论】:

  • 该实现中似乎存在错误。如果将它包装在 BlockingCollection 中并调用 Add 5 次,它会将项目以错误的顺序放入公开的集合中。如果您深入研究blockingConcurrentPriorityQueue 的私有成员,您可以看到底层CPQ 本身以正确的顺序包含正确的数据。但是暴露的集合是乱序的(CPQ 包含 0,1,2,3,4;暴露的集合包含 0,4,3,2,1)。所以 - 不要将此版本用作阻塞集合的一部分。
  • 但是使用哪一个呢?你知道什么更好的吗?
  • @Joe:我也尝试过将它包装到 BlockingCollection 中,但您的问题并没有为我重现......所以如果您有更多关于如何重现问题的详细信息 - 我会很感激的!
【解决方案2】:

您可能需要自己动手。一个相对简单的方法是有一个规则队列数组,优先级降低。

基本上,您将插入队列以获得适当的优先级。然后,在消费者方面,您将沿着列表向下,从最高优先级到最低优先级,检查队列是否非空,如果是,则使用一个条目。

【讨论】:

  • +1,我忘了提如何实现,因为我觉得它很直截了当:) 没有这个,我的答案是不完整的。
  • 这算不上什么实现。可以通过在插入 any 队列后发出信号的 ManualResetEvent 来改进它,以便消费者可以轮询一次,然后等待信号触发。在实践中,最好是等待时间有限且相对较短(可能是四分之一秒),以避免信号丢失的情况。
  • Paw 的实现不必要地锁定,但整体结构是一个很好的起点。
【解决方案3】:

也许您可以使用我自己的 PriorityQueue 实现。它实现了比通常的 push/pop/peek 更多的功能,每当我发现需要它时我就实现了这些功能。它还具有并发锁。

非常感谢您对代码的评论:)

public class PriorityQueue<T> where T : class
{
    private readonly object lockObject = new object();
    private readonly SortedList<int, Queue<T>> list = new SortedList<int, Queue<T>>();

    public int Count
    {
        get
        {
            lock (this.lockObject)
            {
                return list.Sum(keyValuePair => keyValuePair.Value.Count);
            }
        }
    }

    public void Push(int priority, T item)
    {
        lock (this.lockObject)
        {
            if (!this.list.ContainsKey(priority))
                this.list.Add(priority, new Queue<T>());
            this.list[priority].Enqueue(item);
        }
    }
    public T Pop()
    {
        lock (this.lockObject)
        {
            if (this.list.Count > 0)
            {
                T obj = this.list.First().Value.Dequeue();
                if (this.list.First().Value.Count == 0)
                    this.list.Remove(this.list.First().Key);
                return obj;
            }
        }
        return null;
    }
    public T PopPriority(int priority)
    {
        lock (this.lockObject)
        {
            if (this.list.ContainsKey(priority))
            {
                T obj = this.list[priority].Dequeue();
                if (this.list[priority].Count == 0)
                    this.list.Remove(priority);
                return obj;
            }
        }
        return null;
    }
    public IEnumerable<T> PopAllPriority(int priority)
    {
        List<T> ret = new List<T>();
        lock(this.lockObject)
        {
            if (this.list.ContainsKey(priority))
            {
                while(this.list.ContainsKey(priority) && this.list[priority].Count > 0)
                    ret.Add(PopPriority(priority));
                return ret;
            }
        }
        return ret;
    }
    public T Peek()
    {
        lock (this.lockObject)
        {
            if (this.list.Count > 0)
                return this.list.First().Value.Peek();
        }
        return null;
    }
    public IEnumerable<T> PeekAll()
    {
        List<T> ret = new List<T>();
        lock (this.lockObject)
        {
            foreach (KeyValuePair<int, Queue<T>> keyValuePair in list)
                ret.AddRange(keyValuePair.Value.AsEnumerable());
        }
        return ret;
    }
    public IEnumerable<T> PopAll()
    {
        List<T> ret = new List<T>();
        lock (this.lockObject)
        {
            while (this.list.Count > 0)
                ret.Add(Pop());
        }
        return ret;
    }
}

【讨论】:

  • 除了不遵循 .NET 约定之外,它看起来正确但速度慢。缓慢来自锁定所有内容,而 .NET 4.0 并发队列是无锁的。见:codethinked.com/post/2010/02/04/…
  • 无论如何,整体结构+1。
  • 不遵循 .NET 约定是什么意思?
  • 我正在考虑将 SortedList 中的队列设置为 ConcurrentQueue,然后仅在 SortedList 中添加或删除新项目时锁定,而不是在处理队列时锁定。但是在检查 SortedList 中是否存在给定的优先级时,我仍然需要锁定,所以这并没有太大帮助。
  • 这个实现stackoverflow.com/a/4994931/206730 怎么样?哪个更好?
【解决方案4】:

嗯,7 年过去了,但为了后代,我想用我的实现来回答。

Documentation: Optionally awaitable simple to use Concurrent Priority Queue

Sourcecodes: github

nuget package

  • 无锁,
  • 高度并发,
  • 存储项目类型中的泛型,
  • 优先级类型通用,但受限于 .net 枚举表示的优先级,强类型优先级,
  • 在构建过程中明确定义了优先级的降序,
  • 能够检测项目计数和每个优先级项目计数,
  • 能够出队 - 优先级降序,
  • 能够覆盖出队优先级,
  • 可能等待,
  • 可能基于优先级的等待,

【讨论】:

  • 如果您链接到具有优先级队列的库,而不是链接到包含可下载 zip 文件的 CodeProject 文章,其中包含对包含优先级队列的 nuget 包的引用,这可能会更好。或者您可以添加一个抽象级别并通过 bit.ly 或 goo.gl 进一步混淆它
  • 这篇文章说明了一切。如何使用它以及从 github/nuget 下载它的位置。我强迫没有人从文章中下载 zip 文件。我只是觉得很高兴放弃文章简单的例子。文末有github源码和nuget的链接。我在 github 文档方面有点落后,我将在明年继续工作。
【解决方案5】:

由于所有当前答案都已过时或不提供可行的解决方案,因此有一个可用的 implementation on MSDN。请注意,在此实现中优先处理较低的优先级。

【讨论】:

【解决方案6】:

检查Thread-safe Collections in .NET Framework 4 and Their Performance Characteristics 但AFAIK 没有准备好使用的优先级队列。所有新的线程安全集合都不会维护顺序,但您可以在它们之上创建自己的集合。检查@Steven 的方式。

【讨论】:

    【解决方案7】:

    选项:

    1) 如果您的队列永远不会变大,请使用堆并为每次插入和删除锁定整个结构。

    2) 如果你的队列会变大,你可以使用这样的算法:

    http://www.research.ibm.com/people/m/michael/ipl-1996.pdf

    该算法允许多个线程同时处理堆结构,而不会因为一次支持只对树的一部分进行细粒度锁定而面临损坏或死锁的风险。您必须进行基准测试,看看额外锁定和解锁操作的开销是否比锁定整个堆的争用成本更高。

    3) 如果您的目标是完全避免锁定,则上面链接中提到的另一种算法建议使用 FIFO 请求队列(无需锁定即可轻松实现)和一个单独的线程,这是唯一接触堆的东西.您必须进行测量,以了解使用同步对象在线程之间切换焦点的开销与普通直接锁定的开销相比如何。

    在您开始之前,有必要了解一下在使用锁定的简单实现上的命中有多糟糕。它可能不是最有效的实现,但如果它的执行速度仍然比您需要的速度快几个数量级,那么维护的便利性(也就是说,任何人,包括您现在一年都可以,能够简单地查看代码并了解它的作用)可能会超过在排队机制中占用的 CPU 时间的一小部分。

    希望这会有所帮助:-)

    【讨论】:

    • 稍微细说一下——值得注意的是,2)中算法中锁操作的次数与树的高度成正比,即O(lg n)。因此,每次您想要添加一个锁定/解锁操作时,您都需要将排队的项目数量加倍
    • 另外,为了澄清3),我没有直接阅读参考论文,但我相信这个想法是呈现给想要入队或出队项目的消费者的前端基本上是一个适配器到运行实际请求的线程。在任何一种情况下,请求结构都使用 ManualResetEvent 之类的东西构建并放入 FIFO。然后它等待事件。请求处理器线程拾取它,完成工作,并在继续之前设置事件。这会在不使用锁的情况下序列化对堆的所有访问,但阻塞操作和线程切换可能同样糟糕。
    • 值得注意的是,只有消费者实际上必须等待他们的请求。生产者可以“解雇并忘记”。 :-)
    【解决方案8】:

    最近,我正在创建一个需要时间戳事件的状态机。我需要有自己的 ID 的定时事件,而不仅仅是一个简单的时钟滴答声,这样我就可以区分一个事件和另一个事件。

    研究这个问题让我想到了使用优先级队列。我可以以任何顺序将定时事件及其信息排入队列;优先级队列将负责正确地对事件进行排序。计时器将定期检查优先级队列,以查看是否到了队列头部的事件触发的时间。如果是这样,它将使事件出队并调用与其关联的委托。这种方法正是我想要的。

    在 CodeProject 上搜索

    https://www.codeproject.com/Articles/13295/A-Priority-Queue-in-C

    我发现已经编写了一个优先级队列[^] 类。但是,我突然想到,我可以使用我的老朋友跳过列表轻松编写自己的内容。这样做的好处是出队操作只需要 O(1) 时间,而入队操作平均仍然是 log(n)。我认为以这种方式使用跳过列表很新颖,值得单独写一篇文章。

    原来如此。我希望你觉得它很有趣。

    【讨论】:

      【解决方案9】:

      我找到了并发优先级队列here 的一个很好的例子。 希望对你有一点帮助。

      var priorityQueue = new ConcurrentPriorityQueue<TKey, TValue>();
      
      此队列上下文中的

      TKey 可以是 int 值或任何其他实现 IComparable 的对象。

      要使用这样的队列,您可以执行以下操作:

      var priorityQueue = new ConcurrentPriorityQueue<int, object>(); 
      
      // Add elements
      priorityQueue.Enqueue(2, elementP2); 
      priorityQueue.Enqueue(1, elementP1);
      
      // Here you will receive elementP1
      bool result = priorityQueue.TryDequeue(out KeyValuePair<int, object> element);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-12-20
        • 1970-01-01
        • 2017-06-16
        • 2013-02-13
        • 1970-01-01
        • 1970-01-01
        • 2012-02-24
        相关资源
        最近更新 更多