简体   繁体   English

过度使用Interlocked.exchange?

[英]Overuse of Interlocked.exchange?

I am trying to understand correct usage of Interlocked.Exchange, so I am implementing a simple sorted LinkedList with add and remove functionality. 我试图了解Interlocked.Exchange的正确用法,所以我正在实现具有添加和删除功能的简单排序的LinkedList。

If this was not a threadsafe list, obviously to find the insertion point, you'd have something like the below to find the correct point to insert then new node. 如果这不是线程安全列表,显然是要找到插入点,您将具有以下类似内容,以找到要插入的正确点,然后再插入新节点。

    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;
    }

Below is my take on how you'd have to do this for a concurrent list. 以下是我对如何为并发列表执行此操作的看法。 Is there too much Interlocked.Exchange's going on? Interlocked.Exchange正在进行太多吗? Without this, would the insert still be threadsafe? 没有这个,插入是否仍然是线程安全的? Would hundreds or thousands of Interlocked operations cause bad performance? 数百或数千个互锁操作会导致性能下降吗?

    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;
    }

I understand that, for example, curr = curr.next is an atomic read, but can I be sure that a specific thread will read the most up to date value of curr.next, without the Interlocked? 我知道,例如,curr = curr.next是原子读取,但是我可以确定特定线程在没有互锁的情况下将读取curr.next的最新值吗?

Using an Interlocked method does two things: 使用Interlocked方法有两件事:

  1. It performs some series of operations that normally wouldn't be atomic, and makes them effectively atomic. 它执行通常不是原子的一系列操作,并使它们有效地成为原子。 In the case of Exchange you doing the equivalent of: var temp = first; first=second; return temp; 对于Exchange您可以执行以下操作: var temp = first; first=second; return temp; var temp = first; first=second; return temp; but without risk of either variable being modified by another thread while you're doing that. 但这样做时,没有任何风险被另一个线程修改的风险。
  2. It introduces a memory barrier. 它引入了内存障碍。 It's possible for compiler, runtime, and or hardware optimizations to result in different threads having a local "copy" of a value that is technically in shared memory (normally as a result of caching variables). 编译器,运行时和/或硬件优化可能会导致不同的线程具有本地“副本”值,该值在技术上位于共享内存中(通常是由于缓存变量)。 This can result in it taking a long time for one thread to "see" the results of a write in another thread. 这可能导致一个线程花费很长时间才能“看到”另一线程中的写入结果。 A memory barrier essentially syncs up all of these different versions of the same variable. 内存屏障实际上同步了同一变量的所有这些不同版本。

So, onto your code specifically. 因此,专门针对您的代码。 Your second solution isn't actually thread safe. 您的第二个解决方案实际上不是线程安全的。 Each individual Interlocked operation is atomic, but any number of calls to various Interlocked calls aren't atomic. 每个单独的Interlocked操作都是原子的,但是对各种Interlocked调用的任意数量的调用都不是原子的。 Given everything that you're methods are doing, your critical sections are actually much larger; 考虑到方法所执行的所有操作,实际上关键部分要大得多。 you'll need to use lock or another similar mechanism (ie a semaphore or monitor) to limit access to sections of code to only a single thread. 您将需要使用lock或其他类似的机制(即信号量或监视器)来将对代码段的访问限制为仅一个线程。 In your particular case I'd imagine that the entirety of the method is a critical section. 在您的特殊情况下,我想整个方法是关键部分。 If you're really, really careful you may be able to have several smaller critical blocks, but it will be very difficult to ensure that there are no possible race conditions as a result. 如果您真的很谨慎,也许可以有几个较小的关键区,但是要确保没有可能出现的竞赛条件将非常困难。

As for performance, well, as it is the code doesn't work, and so performance is irrelevant. 至于性能,因为代码不起作用,因此性能无关紧要。

Implementing a lock-free linked list is quite a bit more complicated than this. 实现无锁的链表比这要复杂得多。 I strongly recommend that you don't do it. 我强烈建议您不要这样做。 Just use plain old monitors instead. 只需使用普通的旧显示器即可。 If this is just for interests sake, than you need to do some research into lock-free algorithms. 如果只是出于兴趣,那么您需要对无锁算法进行一些研究。

To more directly answer your question, the cost of an interlocked operation depends on the amount of contention over the variables. 为了更直接地回答您的问题,互锁操作的成本取决于变量的争用量。 In a contention free scenario (eg single threaded), the cost is extremely minimal. 在无竞争的情况下(例如单线程),成本极低。 As the number of concurrent accesses rise, the cost increases. 随着并发访问次数的增加,成本也随之增加。 The reason is that an interlocked operation isn't really lock free. 原因是互锁操作并非真正无锁。 It's simply using a hardware lock instead of a software one. 它只是使用硬件锁而不是软件锁。 For this reason, lock-free algorithms often do not perform as well as you would intuitively expect. 因此,无锁算法的性能通常不如您所期望的那样好。

You are only protecting the assignments, but they are atomic anyway. 您只是在保护分配,但是无论如何它们都是原子的。 You should check if the Exchanged actually succeeded. 您应该检查Exchanged是否真的成功。

But that still wouldn't make your list thread-safe, a concurrent thread could be inserting a new value larger than your newValue before your insert point. 但这仍然不能使列表成为线程安全的,并发线程可能会在插入点之前插入一个比newValue大的新值。 I think the list could even end up being broken. 我认为该列表甚至可能最终被打破。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM