C# 消息队列思维
我们使用C#C# 消息队列思维,c#,.net,.net-3.5,queue,C#,.net,.net 3.5,Queue,我们使用C#队列实现了一个消息队列。我们知道只有一个消费者可以从队列中取出可用的消息,以便使用while循环进行处理。我们还知道只有一个生产者将消息放入队列 我们在上面的消息队列上有一个锁,以确保消费者和生产者不能同时访问队列 我的问题是,lock有必要吗?如果队列在实际添加项目后增加其计数属性,并且如果消费者在检索之前检查计数,则即使我们没有该锁定,消费者也应获得完整的消息项目。对吗?因此,我们将不会面临部分消息项问题。然后我们就可以摆脱那锁了 lock会减慢系统的速度,有时我们会看到检索线程
队列
实现了一个消息队列。我们知道只有一个消费者可以从队列中取出可用的消息,以便使用while
循环进行处理。我们还知道只有一个生产者将消息放入队列
我们在上面的消息队列上有一个锁
,以确保消费者和生产者不能同时访问队列
我的问题是,lock
有必要吗?如果队列
在实际添加项目后增加其计数
属性,并且如果消费者在检索之前检查计数
,则即使我们没有该锁定
,消费者也应获得完整的消息项目。对吗?因此,我们将不会面临部分消息项问题。然后我们就可以摆脱那锁了
lock
会减慢系统的速度,有时我们会看到检索线程被阻塞了一段时间,因为我们有一个非常重的生成器
编辑:
不幸的是,我们使用的是.Net 3.5。真正的问题是,队列的内部数据结构可能会在排队或退队时发生变化,并且在此期间,从另一个线程的角度来看,数据结构处于不确定状态 例如,排队可能需要扩展内部数据结构,其中创建新结构,并将旧项从旧结构复制到新结构。在这个过程中会涉及许多步骤,在任何时候,由于操作尚未完成,另一个线程访问队列都是危险的 因此,在排队\出列期间,您必须锁定以使这些操作在逻辑上看起来是原子的
您可以尝试.Net 4.0中的新类,因为它可能具有更好的性能特征,因为它使用了非锁定算法。请参阅。如果您使用的是
队列,则锁定是必需的。您可以通过用“代码> CONTRONGROUND队列
替换它来轻松地删除该代码,但是,您可能需要考虑通过用.< /P>替换该代码来简化该代码。
这将允许您的使用者在检查时消除锁和,只需在上执行一次foreach。生产者可以消除锁定并根据需要添加项目。它还可以很容易地扩展到使用多个生产者,因为您提到目前有一个“非常重的生产者”;该类不是线程安全的。如果您在collection.GetConsumingEnumerable()
中使用System.Collections
,则有一个线程安全的队列
方便(队列
返回这样的System.Collections.Queue.Synchronized()
)。否则,请确保使用提供的对象队列
进行同步:Queue.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(); } } }
使用System.Collections.Generic; 公共静态类Q_示例 { 私有只读队列q=新队列(); 公共无效方法1(int val) { 锁(q.SyncRoot) { q、 排队(val); } } 公共int方法2() { 锁(q.SyncRoot) { 返回q.Dequeue(); } } }
即使您使用的是.NET 3.5,也可以使用ConcurrentQueue。包括以前是.NET 3.5的并行扩展,它是.NET 4.0中包含的任务并行库的前身。不,这不会一直起作用。。。为什么? 让我们从两个线程(一个读线程和一个写线程)中分解将要并发调用的两个方法:给定上述代码,当(且仅当)队列中有足够的容量时,其中三个变量是安全的。_array、_head和_tail的字段要么未修改,要么仅在上述两种方法中的一种中修改public T出列() { 如果(此._size==0) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation\u EmptyQueue); } T local=this._数组[this._头]; this.\u数组[this.\u头]=默认值(T); this.\u head=(this.\u head+1)%this.\u array.Length; 这个; 这个; 返回本地; } 公共无效排队(T项) { if(this.\u size==this.\u array.Length) { int容量=(int)((this._array.Length*200L)/100L); if(容量<(此数组长度+4)) { 容量=此。_array.Length+4; } 这是设定容量(容量); } this.\u数组[this.\u tail]=项; this.\u tail=(this.\u tail+1)%this.\u array.Length; 这个._size++; 这个; }
无法删除lock()的原因是这两种方法都会修改_size和_version。尽管可以认为可以忽略_版本上的冲突,但_大小上的冲突会导致一些不希望发生的和不可预测的行为。顺便说一句,对于单个读卡器和单个写卡器来说,无锁队列非常容易编写。这是一个非常原始的概念示例,但它确实起到了作用: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(); } }
无锁类 { 类项目 { 其次是公共项目; 布尔有效; T_值; 公共项目(bool有效,T值) { _有效=有效; _价值=价值; Next=null; } 公共bool是有效的{get{return\u valid;}} 公共价值 { T值=_值; _有效=错误; _值=默认值(T); 返回值; } } 第一项; 最后一项;; 公共无锁监狱() { _first=_last=新项(false,默认值(T)); } 公共图书馆是空的 { 得到 { while(!\u first.IsValid&&u first.Next!=null) _first=_first.Next; 返回false==\u first.IsValid; } } 公共无效排队(T值) { 项目i=新项目(真,值
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(); } }