过度使用Interlocked.exchange?

时间:2012-10-19 17:31:08

标签: c# multithreading .net-4.0 lockless

我试图理解Interlocked.Exchange的正确用法,所以我实现了一个带有添加和删除功能的简单排序LinkedList。

如果这不是一个线程安全列表,显然要找到插入点,你会有类似下面的内容来找到插入新节点的正确点。

    public void Insert(int newValue)
    {
        var prev = _header;
        Node curr = _header.Next;

        while(curr != null && curr.value > newValue )
        {
            prev = curr;
            curr = curr.Next;
        }
        var newNode = new Node(newValue, curr);
        prev.Next = newNode;
    }

以下是我对如何为并发列表执行此操作的看法。是否有太多Interlocked.Exchange正在进行?如果没有这个,插入物仍然是线程安全的吗?数百或数千个互锁操作会导致性能下降吗?

    public void InsertAsync(int newValue)
    {
        var prev = _header;
        Node curr = new Node(0, null);
        Interlocked.Exchange(ref curr, _header.Next);

        while (curr != null && curr.value > newValue)
        {
            prev = Interlocked.Exchange(ref curr, curr.Next);
        }
        //need some locking around prev.next first, ensure not modified/deleted, etc..
        //not in the scope of this question.
        var newNode = new Node(newValue, prev.Next);
        prev.Next = newNode;
    }

据我所知,例如,curr = curr.next是一个原子读取,但是我可以确定一个特定的线程会读取curr.next的最新值,而没有Interlocked吗?

3 个答案:

答案 0 :(得分:5)

使用Interlocked方法可以做两件事:

  1. 它执行一系列通常不会是原子的操作,并使它们成为原子。在Exchange的情况下,您执行的操作相当于:var temp = first; first=second; return temp;,但在您执行此操作时,没有任何一个变量被另一个线程修改的风险。
  2. 它引入了一个记忆障碍。编译器,运行时和/或硬件优化可能导致不同的线程具有技术上位于共享内存中的值的本地“副本”(通常是缓存变量的结果)。这可能导致一个线程花费很长时间“看到”另一个线程中的写入结果。内存屏障基本上同步了同一变量的所有这些不同版本。
  3. 所以,特别是你的代码。你的第二个解决方案实际上不是线程安全每个单独的Interlocked操作都是原子操作,但对各种Interlocked调用的任何调用都不是原子操作。鉴于你所做的一切,你的关键部分实际上要大得多;您需要使用lock或其他类似机制(即信号量或监视器)来限制对单个线程的代码段的访问。在您的特定情况下,我认为整个方法是一个关键部分。如果你真的非常小心,你可能会有几个较小的关键区块,但是很难确保没有可能的竞争条件。

    至于性能,因为代码不起作用,因此性能无关紧要。

答案 1 :(得分:4)

实现无锁链表比这复杂得多。我强烈建议你不要这样做。只需使用普通的旧显示器。如果这只是为了利益,那么你需要对无锁算法进行一些研究。

为了更直接地回答您的问题,互锁操作的成本取决于对变量的争用程度。在无争用场景(例如单线程)中,成本非常低。随着并发访问次数的增加,成本增加。原因是互锁操作并非真正无锁。它只是使用硬件锁而不是软件锁。出于这个原因,无锁算法的效果通常不如您所预期的那样好。

答案 2 :(得分:3)

你只是在保护作业,但无论如何它们都是原子的。您应该检查Exchanged是否实际成功。

但是这仍然不会使您的列表成为线程安全的,并发线程可能会在插入点之前插入一个大于newValue的新值。我认为这份名单甚至可能最终被打破。