【问题标题】:ThreadSafe LinkedList Implementation线程安全的链表实现
【发布时间】:2011-05-06 21:03:03
【问题描述】:

我知道链表不是线程安全的,在工作中我被要求编写一个简单的线程安全链表。

由于我不会经历各种复杂情况,我不能简单地包装一个 LinkedList,而是需要编写一个 LinkedList 的实现

我猜我需要这个,但我如何才能以线程安全的方式实际实现一个枚举器(用于 linedlist)?

    public class LinkedlistNode
    {
        private LinkedlistNode next;
        private T item;
        /// <summary>
        /// Constructor for a new LinklistNode
        /// </summary>
        /// <param name="node">The node item to create</param>
        public LinkedlistNode(T node)
        {
            next = null;
            item = node;
        }
        /// <summary>
        /// Shows the next item in the collection (or shows null for the the last item)
        /// </summary>
        public LinkedlistNode Next
        {
            get { return next; }
            set { next = value; }
        }
        /// <summary>
        /// The contents of the list
        /// </summary>
        public T Item         
        {             
            get { return item; }             
            set { item = value; }         
        }            
    }

【问题讨论】:

  • 看起来你有一个单链表。如果您通过取出属性上的设置器使其不可变,那么您的链接列表将本质上是线程安全的。您可以在FSharp.Core.dll 中找到已经为您实现的一个。
  • 我需要能够添加和删除项目 :)
  • 如果另一个线程试图从列表中添加或删除一个项目,而一个线程正在枚举它,会发生什么?
  • 为了有效地删除项目,您需要一个双向链表。否则,您必须通过从头开始并遍历直到找到其下一项为当前节点的节点来获取要删除的节点的所有者。 Previous 属性采用该线性运算并使其保持不变。
  • 使用单链接不可变列表很容易将项目添加到前面 - 只需创建一个节点,其中 next 指向现有列表的第一个节点。删除一个节点(或在中间添加)需要重新生成该节点之前的所有节点,这是真的。

标签: c# multithreading


【解决方案1】:

好的开始。首先,我会通过包含类似于 Next 的 Previous 属性来双重链接列表。

使链表线程安全的主要问题是,与索引集合不同,最多必须同时锁定三个对象才能执行添加和删除。这会增加死锁的可能性,例如,如果另一个线程正在枚举列表;添加/删除线程需要锁定第四个节点才能删除第五个节点,而枚举线程已经锁定了第四项,需要锁定第五个。单链表也会有同样的问题,因为该算法需要另一个枚举 top 确定“前一个”节点,这最终会阻止试图到达已经被第一个等待到达的枚举器锁定的第四项第五项。

我想有一个大问题要问:您究竟需要如何添加和删除项目?如果此实现将用作堆栈或队列后面的集合,则使其成为线程安全变得容易得多,因为不允许枚举列表,并且在当前列表中的节点中,只有端点节点(一个用于 Stack,2 个用于队列)在添加/删除时需要锁定,除非堆栈或队列只有一项,否则锁定这些节点只会阻塞其他尝试添加或删除的线程。

如果这是一个完整的链表实现,在导航到任何项目以及从任何位置添加和删除方面需要与 List 类似的功能,那么我认为你最好的选择是将节点隐藏在一个会锁定自身的包装器后面在执行任何操作之前,就像 Interlocked 对整数类型所做的那样。这绝不是一种“细粒度”的方法;任何想对列表做任何事情的线程都必须等待轮到它。尝试允许多个线程同时访问时,死锁的机会太多了。

对于没有死锁的细粒度、线程安全的锁定,您唯一的希望是始终按照枚举列表的相同顺序获取锁,并且只允许在一个方向上进行迭代。基本上,这需要您隐藏双向链表的“上一个”节点,并允许节点获取其他节点上的“持久”锁。

【讨论】:

  • 需要锁定哪三个对象以及如何在枚举器中确保线程安全?我知道客户端调用 GetSize 的问题,然后假设列表没有更改。
  • 删除节点时,必须锁定它之前的节点、要删除的节点以及之后的节点。在操作发生时,任何其他线程都不应该能够获得对任何这些节点的引用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-12-18
  • 1970-01-01
  • 1970-01-01
  • 2010-10-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-02
相关资源
最近更新 更多