仅在满足条件时才从ConcurrentQueue中出队

时间:2015-05-19 21:55:52

标签: c# multithreading concurrency locking queue

如果满足某些条件,我怎样才能使ConcurrentQueue的下一个元素出列?

E.g。如果要出列的下一个项目满足某个条件,则将其出列,否则将其保留。基本上是'DequeueIf''TryDequeueIf'方法

示例:

var myQueue = new ConcurrentQueue<int>()
...
int item;
// only dequeue if the next item is 0
bool success = myQueue.TryDequeueIf(out item, x=>x==0) 

当然可以先调用TryPeek,检查条件,然后TryDequeue,但这不再是线程安全的。

我可以将整个TryPeek & TryDequeue包装成一个锁,但这有点违背了使用ConcurrentQueue的目的;并且意味着所有正常的无条件出列也必须被锁定。我不确定我是否甚至必须锁定每个Enqueue才能保存。如果可能的话,我想避免实施我自己的锁定策略可能的陷阱。

是否有使用.net4.0 ConcurrentQueue类或其他并发类之一的无锁解决方案?

3 个答案:

答案 0 :(得分:3)

使用内置方法无法做到这一点。怎么办?

  1. 编写自己的简单队列。只需为每个队列使用一个锁。除非队列中有非常的高流量,否则这样做会很好。无争用锁每个周期消耗两个互锁操作。
  2. 使用互锁操作编写复杂的队列。使用CAS重试循环可以实现谓词原子获取操作。您可能可以使用BCL源代码作为起点或灵感。
  3. 放弃拥有队列的想法。在非队列上执行此操作很容易。如果您绘制错误的项目,只需将其插回队列即可。

答案 1 :(得分:2)

作为@usr answer的附加组件:

  

我可以整理TryPeek&amp;锁定TryDequeue,但这有点违背了使用ConcurrentQueue

的目的

不是真的,或者至少不完全,因为其主要目的之一是协调生产者/消费者集合点,在这种情况下使用锁定不会阻止任何生产者,只阻止其他消费者。但这确实意味着您将需要对所有其他变异读取操作使用相同的锁。

因此,如果这是不可接受的,您将不得不

  1. 滚动您自己的并发队列或针对您的特定使用模式优化的更好的数据结构(堆?)实现。
  2. 考虑一种可以更好地满足您需求的替代方法:使用&#34;推送&#34;或&#34;事件&#34;基于模型而不是&#34;拉&#34;基于一个。使用例如使用where过滤器观察的Rx和消费者。

答案 2 :(得分:1)

大多数concurrentQueue实现使用无锁实现允许许多生产者和使用者同时编写队列而不会相互阻塞(除了偶尔的旋转重试)

条件出队的基本要求是'pop if top on'函数的实现:'bool dequeueIf(T)//如果T仍然是头,则退出T,如果不是头,则返回false;这允许你:

T t=null;
do{ // need to spin in case the head changes between the peek and dequeue attempt
    done=-1; // assume failure
    T t2=peek(), 
    if (t!=null && someComplexCondition(t)) {// then it's what we want
        if(!dequeueIf(T)) // check to see if the head is changed
             {done=0;} // spin if the head is changed
        else {done=1;t=t2;} // it meets the criteria, and we de-queued it.
    }
} while (done==0);
// done = -1 == head does not meet criteria
// done = 1 == T contains poped item that met criteria

dequeueIf(T)需要在队列中实现。

如果是.Net ConcurrentQueue,则需要修改版本Dequeue(输出T结果):Dequeue(ref T toRemove)。

这将传递给concurrentQueue.Segment.TryRemove的新版本(输出T结果):concurrentQueue.Segment.TryRemove(ref T toRemove)

此函数需要在执行compareExchange去队列之前,首先将'local'处的T与toRemove进行比较,并且只有当它仍然位于顶部时才将其删除(否则它可以假设另一个线程TryRemove已经弹出关闭队列的值,所以你不能返回有问题的值; toRemove应该是否为空。)

请注意;你不能重载f(out T)和f(ref T),所以返回bool表示你现在'拥有'你请求弹出的T似乎是一个合理的解决方案,否则我会使用f的重载(T toRemove )允许原始函数签名不变。