C# 消息队列思维

C# 消息队列思维,c#,.net,.net-3.5,queue,C#,.net,.net 3.5,Queue,我们使用C#队列实现了一个消息队列。我们知道只有一个消费者可以从队列中取出可用的消息,以便使用while循环进行处理。我们还知道只有一个生产者将消息放入队列 我们在上面的消息队列上有一个锁,以确保消费者和生产者不能同时访问队列 我的问题是,lock有必要吗?如果队列在实际添加项目后增加其计数属性,并且如果消费者在检索之前检查计数,则即使我们没有该锁定,消费者也应获得完整的消息项目。对吗?因此,我们将不会面临部分消息项问题。然后我们就可以摆脱那锁了 lock会减慢系统的速度,有时我们会看到检索线程

我们使用C#
队列
实现了一个消息队列。我们知道只有一个消费者可以从队列中取出可用的消息,以便使用
while
循环进行处理。我们还知道只有一个生产者将消息放入队列

我们在上面的消息队列上有一个
,以确保消费者和生产者不能同时访问队列

我的问题是,
lock
有必要吗?如果
队列
在实际添加项目后增加其
计数
属性,并且如果消费者在检索之前检查
计数
,则即使我们没有该
锁定
,消费者也应获得完整的消息项目。对吗?因此,我们将不会面临部分消息项问题。然后我们就可以摆脱那锁了

lock
会减慢系统的速度,有时我们会看到检索线程被阻塞了一段时间,因为我们有一个非常重的生成器

编辑:


不幸的是,我们使用的是.Net 3.5。

真正的问题是,队列的内部数据结构可能会在排队或退队时发生变化,并且在此期间,从另一个线程的角度来看,数据结构处于不确定状态

例如,排队可能需要扩展内部数据结构,其中创建新结构,并将旧项从旧结构复制到新结构。在这个过程中会涉及许多步骤,在任何时候,由于操作尚未完成,另一个线程访问队列都是危险的

因此,在排队\出列期间,您必须锁定以使这些操作在逻辑上看起来是原子的


您可以尝试.Net 4.0中的新类,因为它可能具有更好的性能特征,因为它使用了非锁定算法。

请参阅。

如果您使用的是
队列,则锁定是必需的。您可以通过用“代码> CONTRONGROUND队列替换它来轻松地删除该代码,但是,您可能需要考虑通过用.< /P>替换该代码来简化该代码。

这将允许您的使用者在检查时消除锁和,只需在
collection.GetConsumingEnumerable()
上执行一次foreach。生产者可以消除锁定并根据需要添加项目。它还可以很容易地扩展到使用多个生产者,因为您提到目前有一个“非常重的生产者”;该类不是线程安全的。如果您在
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中包含的任务并行库的前身。

不,这不会一直起作用。。。为什么?

让我们从两个线程(一个读线程和一个写线程)中分解将要并发调用的两个方法:

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++;
这个;
}
给定上述代码,当(且仅当)队列中有足够的容量时,其中三个变量是安全的。_array、_head和_tail的字段要么未修改,要么仅在上述两种方法中的一种中修改


无法删除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();
    }
}