这是线程安全队列的实现。推和弹出不会相互阻挡。但是,如果队列为空,pop将等待一个项目被推送。多个生产者和消费者可以使用该队列。如果您发现任何问题,请告诉我。
更新:根据答案编辑1)解决了“队列已满”的问题。 2).NET4中有BlockingCollection<T>
和ConcurrentQueue<T>
。所以没有必要重新发明轮子(对于.NET4)
public class CustomQueue<T> where T: class
{
class Node
{
public Node()
{
Value = null;
NextNode = null;
}
public Node(T value)
{
Value = value;
NextNode = null;
}
public T Value;
public Node NextNode;
}
object PushLocker = new object();
object PopLocker = new object();
Semaphore QueueSemaphore;
volatile int PushIncrement;
volatile int PopIncrement;
int MaxItems;
Node FirstNode;
Node LastNode;
public CustomQueue(int maxItems)
{
QueueSemaphore = new Semaphore(0, maxItems);
MaxItems = maxItems;
FirstNode = LastNode = new Node();
PushIncrement = 0;
PopIncrement = 0;
}
public int Size()
{
return PushIncrement - PopIncrement;
}
public bool Push(T value)
{
lock(PushLocker)
{
if((Size()) >= MaxItems)
{
lock(PopLocker)
{
PushIncrement = PushIncrement - PopIncrement;
PopIncrement = 0;
return false;
}
}
Node newNode = new Node(value);
LastNode.NextNode = newNode;
LastNode = newNode;
PushIncrement++;
QueueSemaphore.Release();
return true;
}
}
public T Pop()
{
QueueSemaphore.WaitOne();
lock(PopLocker)
{
Node tempFirst = FirstNode;
Node tempNext = FirstNode.NextNode;
T value = tempNext.Value;
tempNext.Value = null;
FirstNode = tempNext;
PopIncrement++;
return value;
}
}
}
答案 0 :(得分:3)
如果你这样做是为了自我教育,那么很好 - 否则BlockingCollection<T>
或ConcurrentQueue<T>
是不错的选择。
我在这里看到的一个问题是,一旦启动就无法中断Pop
- 它假定一个物体在唤醒时正在等待。如何在终止时明确这一点?返回TryPop
(带有元素)或true
(如果没有数据)的false
可能会更好,那么一旦队列耗尽,您就可以发信号等待线程干净地关闭。< / p>
答案 1 :(得分:3)
1.
考虑添加第二个Node构造函数:
public Node(T value)
{
Value = value;
}
然后是您的客户代码:
Node newNode = new Node();
newNode.Value = value;
可以将值视为不变量:
Node newNode = new Node(value);
2.
然后创建公共字段:
public T Value;
public Node NextNode;
进入自动属性:
public T Value { get; private set; };
public Node NextNode { get; set; };
因此,您可以从实现中抽象出使用情况,并在事后添加验证,其他处理等,同时最大限度地减少对客户端代码的干扰。
答案 2 :(得分:3)
它看起来像一个很好的实现一目了然。使用不同的锁对我来说总是一个红旗,所以我仔细查看了一些的边缘情况,同时调用了Pop
和Push
,看起来很安全。我怀疑你可能在阻塞队列的链表实现上自学了吗?这是安全的原因是因为您只引用了来自LastNode
的{{1}}和来自Push
的{{1}},否则整个伎俩将会崩溃。
现在唯一突出的问题是,当你试图从FirstNode
中释放一个计数时,如果它已经满了就会抛出异常,所以你可能想要防范它。 sup> 1 否则你最终会在链表中增加额外的节点,队列将1)拥有超过最大数量的项目,2)它将被实时锁定。
<强>更新强>
我更多地考虑了这个问题。 Pop
方法中的Semaphore
调用将会非常有问题。我只看到一种可能的解决方案。从构造函数中删除Release
参数,并允许信号量计数到Push
。否则,您将不得不从根本上改变您的方法,从而导致实施与您目前所处的相近。
1 我想您会发现这比您可能立即意识到的更困难。
答案 3 :(得分:2)
如果你有.Net 4
,请看这个答案 4 :(得分:0)
由于我是不可变对象的粉丝,这里可以替代我之前的答案,我会考虑更清洁:
public sealed class CustomQueue<T> where T : class
{
private readonly object pushLocker = new object();
private readonly object popLocker = new object();
private readonly Semaphore queueSemaphore;
private readonly int maxItems;
private volatile int pushIncrement;
private volatile int popIncrement;
private Node firstNode = new Node();
private Node lastNode;
public CustomQueue(int maxItems)
{
this.maxItems = maxItems;
this.lastNode = this.firstNode;
this.queueSemaphore = new Semaphore(0, this.maxItems);
}
public int Size
{
get
{
return this.pushIncrement - this.popIncrement;
}
}
public bool Push(T value)
{
lock (this.pushLocker)
{
if (this.Size >= this.maxItems)
{
lock (this.popLocker)
{
this.pushIncrement = this.pushIncrement - this.popIncrement;
this.popIncrement = 0;
return false;
}
}
Node newNode = new Node(value, this.lastNode.NextNode);
this.lastNode = new Node(this.lastNode.Value, newNode);
this.firstNode = new Node(null, newNode);
this.pushIncrement++;
this.queueSemaphore.Release();
return true;
}
}
public T Pop()
{
this.queueSemaphore.WaitOne();
lock (this.popLocker)
{
Node tempNext = this.firstNode.NextNode;
T value = tempNext.Value;
this.firstNode = tempNext;
this.popIncrement++;
return value;
}
}
private sealed class Node
{
private readonly T value;
private readonly Node nextNode;
public Node()
{
}
public Node(T value, Node nextNode)
{
this.value = value;
this.nextNode = nextNode;
}
public T Value
{
get
{
return this.value;
}
}
public Node NextNode
{
get
{
return this.nextNode;
}
}
}
}