我们使用C#Queue
实现了一个消息队列。我们知道我们只有一个消费者可以从队列中获取可用消息,以便使用while
循环进行处理。我们也知道只有一个生产者将消息放入队列。
我们在上面的消息队列中有一个lock
,以确保消费者和生产者不能同时访问队列。
我的问题是lock
是否必要?如果Queue
增加其Count
属性 AFTER ,则实际添加了一个项目,如果消费者在检索之前检查Count
,则消费者应该获得完整的消息项目即使我们没有lock
。对?因此,我们不会面临部分邮件项目问题。然后我们可以摆脱lock
?
lock
会降低系统的速度,偶尔我们可以看到检索线程暂时被阻止,因为我们有一个非常繁重的生产者。
修改
不幸的是我们正在使用.Net 3.5。
答案 0 :(得分:10)
真正的问题是队列的内部数据结构在发生入队或出队时可能会发生变异,在此期间,数据结构处于与另一个线程的观点不确定的状态。 / p>
例如,入队可能需要扩展内部数据结构,其中创建新结构,旧项目从旧结构复制到新结构。此过程将涉及许多步骤,其中任何时候由于操作未完成而导致另一个线程访问队列将是危险的。
因此,在enqueue \ dequeue期间,您必须锁定以使这些操作看起来具有逻辑原子性。
您可以在.Net 4.0中尝试新的ConcurrentQueue类,因为它可能具有更好的性能特征,因为它使用非锁定算法。
答案 1 :(得分:4)
如果您使用的是Queue<T>
,则需要锁定。您可以使用ConcurrentQueue<T>
替换它来轻松删除它,但是,您可能需要考虑通过将其替换为BlockingCollection<T>
来简化此代码。
这将允许您的消费者在检查时消除锁定,并且只需在collection.GetConsumingEnumerable()
上执行一次预告。生产者可以消除锁定并根据需要添加项目。它也很容易让你扩展到使用多个生产者,因为你提到你现在有一个“非常沉重的生产者”。
答案 2 :(得分:2)
请参阅ConcurrentQueue。
答案 3 :(得分:1)
不,这不会一直有效......为什么?
让我们反汇编我们将要从两个线程(一个读取和一个写入器)同时调用的两个方法:
public T Dequeue()
{
if (this._size == 0)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EmptyQueue);
}
T local = this._array[this._head];
this._array[this._head] = default(T);
this._head = (this._head + 1) % this._array.Length;
this._size--;
this._version++;
return local;
}
public void Enqueue(T item)
{
if (this._size == this._array.Length)
{
int capacity = (int) ((this._array.Length * 200L) / 100L);
if (capacity < (this._array.Length + 4))
{
capacity = this._array.Length + 4;
}
this.SetCapacity(capacity);
}
this._array[this._tail] = item;
this._tail = (this._tail + 1) % this._array.Length;
this._size++;
this._version++;
}
鉴于上述代码,如果(并且仅当)队列中有足够的容量,则三个变量是安全的。 _array,_head和_tail的字段未经修改或仅在上述两种方法中的一种中进行修改。
你无法删除lock()的原因是两个方法都修改了_size和_version。虽然可以忽略_version上的碰撞,但是_size上的碰撞会引起一些不希望的和不可预测的行为。
答案 4 :(得分:1)
class LocklessQueue<T>
{
class Item
{
public Item Next;
bool _valid;
T _value;
public Item(bool valid, T value)
{
_valid = valid;
_value = value;
Next = null;
}
public bool IsValid { get { return _valid; } }
public T TakeValue()
{
T value = _value;
_valid = false;
_value = default(T);
return value;
}
}
Item _first;
Item _last;
public LocklessQueue()
{
_first = _last = new Item(false, default(T));
}
public bool IsEmpty
{
get
{
while (!_first.IsValid && _first.Next != null)
_first = _first.Next;
return false == _first.IsValid;
}
}
public void Enqueue(T value)
{
Item i = new Item(true, value);
_last.Next = i;
_last = i;
}
public T Dequeue()
{
while (!_first.IsValid && _first.Next != null)
_first = _first.Next;
if (IsEmpty)
throw new InvalidOperationException();//queue is empty
return _first.TakeValue();
}
}
答案 5 :(得分:0)
你应该锁定;这个类不是线程安全的。如果您使用Queue
中的System.Collections
,则会有一个线程安全的Queue
便捷(System.Collections.Queue.Synchronized()
返回此类Queue
)。否则,请务必使用提供的对象Queue<T>.SyncRoot
进行同步:
using System.Collections.Generic;
public static class Q_Example
{
private readonly Queue<int> q = new Queue<int>();
public void Method1(int val)
{
lock(q.SyncRoot)
{
q.EnQueue(val);
}
}
public int Method2()
{
lock(q.SyncRoot)
{
return q.Dequeue();
}
}
}
答案 6 :(得分:0)
ConcurrentQueue
。 Reactive Extensions包括以前的.NET 3.5并行扩展 - .NET 4.0中包含的任务并行库的前身。