创建一个LinkedRingBuffer线程安全的C#实现

时间:2016-03-06 13:11:01

标签: c# performance linked-list thread-safety buffer

我有三个问题:

  1. 您通常会考虑我解决问题的方法吗?
  2. 您认为我可以进一步提高绩效吗?
  3. 最重要的一个:如何让我的实现真的是线程安全的?
  4. 首先是我所处的简化场景: 我通过具有不同设备的消息传递系统进行通信。我在很短的时间内收到并发送了成千上万条消息。我在多线程环境中,所以很多不同的任务都在发送和期待消息。对于消息接收,事件驱动的方法在使线程安全的意义上给我们带来了很多麻烦。 我有一些Receiver任务从外部获取消息,并且必须将这些消息传递给许多消费者任务。

    所以我想出了一个不同的方法: 为什么不具有几千条消息的历史记录,其中每个新消息被排队并且消费者任务可以从最新项目向后搜索到最后处理的项目以便获得所有新到达的消息。当然,这必须是快速且线程安全的。

    我提出了链接环缓冲区的想法并实现了以下内容:

      public class LinkedRingBuffer<T>
      {
         private LinkedRingBufferNode<T> firstNode;
         private LinkedRingBufferNode<T> lastNode;
    
         public LinkedRingBuffer(int capacity)
         {
            Capacity = capacity;
            Count = 0;
         }
    
         /// <summary>
         /// Maximum count of items inside the buffer
         /// </summary>
         public int Capacity { get; }
    
         /// <summary>
         /// Actual count of items inside the buffer
         /// </summary>
         public int Count { get; private set; }
    
         /// <summary>
         /// Get value of the oldest buffer entry
         /// </summary>
         /// <returns></returns>
         public T GetFirst()
         {
            return firstNode.Item;
         }
    
         /// <summary>
         /// Get value of the newest buffer entry
         /// </summary>
         /// <returns></returns>
         public T GetLast()
         {
            return lastNode.Item;
         }
    
         /// <summary>
         /// Add item at the end of the buffer. 
         /// If capacity is reached the link to the oldest item is deleted.
         /// </summary>
         public void Add(T item)
         {
            /* create node and set to last one */
            var node = new LinkedRingBufferNode<T>(lastNode, item);
            lastNode = node;
            /* if it is the first node, the created is also the first */
            if (firstNode == null)
               firstNode = node;
            /* check for capacity reach */
            Count++;
            if(Count > Capacity)
            {/* deleted all links to the current first so that its eventually gc collected */
               Count = Capacity;
               firstNode = firstNode.NextNode;
               firstNode.PreviousNode = null;
            }
         }
    
         /// <summary>
         /// Iterate through the buffer from the oldest to the newest item
         /// </summary>
         public IEnumerable<T> LastToFirst()
         {
            var current = lastNode;
            while(current != null)
            {
               yield return current.Item;
               current = current.PreviousNode;
            }
         }
    
         /// <summary>
         /// Iterate through the buffer from the newest to the oldest item
         /// </summary>
         public IEnumerable<T> FirstToLast()
         {
            var current = firstNode;
            while (current != null)
            {
               yield return current.Item;
               current = current.NextNode;
            }
         }
    
         /// <summary>
         /// Iterate through the buffer from the oldest to given item. 
         /// If item doesn't exist it iterates until it reaches the newest
         /// </summary>
         public IEnumerable<T> LastToReference(T item)
         {
            var current = lastNode;
            while (current != null)
            {
               yield return current.Item;
               if (current.Item.Equals(item))
                  break;
               current = current.PreviousNode;
            }
         }
    
         /// <summary>
         /// Iterate through the buffer from the newest to given item. 
         /// If item doesn't exist it iterates until it reaches the oldest
         /// </summary>
         public IEnumerable<T> FirstToReference(T item)
         {
            var current = firstNode;
            while (current != null)
            {
               yield return current.Item;
               if (current.Item.Equals(item))
                  break;
               current = current.PreviousNode;
            }
         }
    
         /// <summary>
         /// Represents a linked node inside the buffer and holds the data
         /// </summary>
         private class LinkedRingBufferNode<A>
         {
            public LinkedRingBufferNode(LinkedRingBufferNode<A> previousNode, A item)
            {
               Item = item;
               NextNode = null;
               PreviousNode = previousNode;
               if(previousNode != null)
                  previousNode.NextNode = this;
            }
            internal A Item { get; }
            internal LinkedRingBufferNode<A> PreviousNode { get; set; }
            internal LinkedRingBufferNode<A> NextNode { get; private set; }
         }
      }
    

    但不幸的是,我对多线程环境有点新意,那么如何让这个缓冲线程安全地进行多次读写呢?

    谢谢!

1 个答案:

答案 0 :(得分:1)

我认为最简单的方法是在执行线程关键代码时,使用lock进行同步objectlock块中的代码称为critical section,并且一次只能由一个线程访问。任何其他希望访问它的线程都会等待,直到锁被释放。

定义和初始化:

private object Synchro;

public LinkedRingBuffer(int capacity)
{
    Synchro = new object();
    // Other constructor code
}

<强>用法:

public T GetFirst()
{
    lock(Synchro)
    {
        return firstNode.Item;
    }
}

编写线程安全代码时,lock某些部分可能看起来很明显。但是,如果您不确定是否lock声明或代码块,那么对于读写安全,您需要考虑:

  • 此代码是否可以影响任何其他锁定关键部分的行为或结果。
  • 是否有任何其他锁定的关键部分可以影响此代码的行为或结果。

您还需要重写一些自动实现的属性以获得支持字段。它应该非常简单,但是......

您对yield return的使用虽然在单线程上下文中非常智能和高效,但会在多线程上下文中造成麻烦。这是因为yield return doesn't release a lock statement (并且它不应该)。无论在何处使用yield return,都必须在包装器中执行实现。

您的线程安全代码如下所示:

public class LinkedRingBuffer<T>
{
    private LinkedRingBufferNode<T> firstNode;
    private LinkedRingBufferNode<T> lastNode;
    private object Synchro;

    public LinkedRingBuffer(int capacity)
    {
        Synchro = new object();
        Capacity = capacity;
        Count = 0;
    }

    /// <summary>
    /// Maximum count of items inside the buffer
    /// </summary>
    public int Capacity { get; }

    /// <summary>
    /// Actual count of items inside the buffer
    /// </summary>
    public int Count
    {
        get
        {
            lock (Synchro)
            {
                return _count;
            }
        }
        private set
        {
            _count = value;
        }
    }
    private int _count;

    /// <summary>
    /// Get value of the oldest buffer entry
    /// </summary>
    /// <returns></returns>
    public T GetFirst()
    {
        lock (Synchro)
        {
            return firstNode.Item;
        }
    }

    /// <summary>
    /// Get value of the newest buffer entry
    /// </summary>
    /// <returns></returns>
    public T GetLast()
    {
        lock (Synchro)
        {
            return lastNode.Item;
        }
    }

    /// <summary>
    /// Add item at the end of the buffer. 
    /// If capacity is reached the link to the oldest item is deleted.
    /// </summary>
    public void Add(T item)
    {
        lock (Synchro)
        {
            /* create node and set to last one */
            var node = new LinkedRingBufferNode<T>(lastNode, item);
            lastNode = node;
            /* if it is the first node, the created is also the first */
            if (firstNode == null)
                firstNode = node;
            /* check for capacity reach */
            Count++;
            if (Count > Capacity)
            {
                /* deleted all links to the current first so that its eventually gc collected */
                Count = Capacity;
                firstNode = firstNode.NextNode;
                firstNode.PreviousNode = null;
            }
        }
    }

    /// <summary>
    /// Iterate through the buffer from the oldest to the newest item
    /// </summary>
    public IEnumerable<T> LastToFirst()
    {
        lock (Synchro)
        {
            var materialized = LastToFirstInner().ToList();
            return materialized;
        }
    }

    private IEnumerable<T> LastToFirstInner()
    {
        var current = lastNode;
        while (current != null)
        {
            yield return current.Item;
            current = current.PreviousNode;
        }
    }

    /// <summary>
    /// Iterate through the buffer from the newest to the oldest item
    /// </summary>
    public IEnumerable<T> FirstToLast()
    {
        lock (Synchro)
        {
            var materialized = FirstToLastInner().ToList();
            return materialized;
        }
    }

    private IEnumerable<T> FirstToLastInner()
    {
        var current = firstNode;
        while (current != null)
        {
            yield return current.Item;
            current = current.NextNode;
        }
    }

    /// <summary>
    /// Iterate through the buffer from the oldest to given item. 
    /// If item doesn't exist it iterates until it reaches the newest
    /// </summary>
    public IEnumerable<T> LastToReference(T item)
    {
        lock (Synchro)
        {
            var materialized = LastToReferenceInner(item).ToList();
            return materialized;
        }
    }

    private IEnumerable<T> LastToReferenceInner(T item)
    {
        var current = lastNode;
        while (current != null)
        {
            yield return current.Item;
            if (current.Item.Equals(item))
                break;
            current = current.PreviousNode;
        }
    }

    /// <summary>
    /// Iterate through the buffer from the newest to given item. 
    /// If item doesn't exist it iterates until it reaches the oldest
    /// </summary>
    public IEnumerable<T> FirstToReference(T item)
    {
        lock (Synchro)
        {
            var materialized = FirstToReferenceInner(item).ToList();
            return materialized;
        }
    }

    private IEnumerable<T> FirstToReferenceInner(T item)
    {
        var current = firstNode;
        while (current != null)
        {
            yield return current.Item;
            if (current.Item.Equals(item))
                break;
            current = current.PreviousNode;
        }
    }

    /// <summary>
    /// Represents a linked node inside the buffer and holds the data
    /// </summary>
    private class LinkedRingBufferNode<A>
    {
        public LinkedRingBufferNode(LinkedRingBufferNode<A> previousNode, A item)
        {
            Item = item;
            NextNode = null;
            PreviousNode = previousNode;
            if (previousNode != null)
                previousNode.NextNode = this;
        }
        internal A Item { get; }
        internal LinkedRingBufferNode<A> PreviousNode { get; set; }
        internal LinkedRingBufferNode<A> NextNode { get; private set; }
    }
}

可以进行一些优化,例如,您不需要在关键部分内创建LinkedRingBufferNode个对象,但是您必须将lastNode值复制到一个局部变量中。临界区,在创建对象之前。