试图用 SpinWait.SpinUntil() 替换 lock() 但它不起作用

trying to replace a lock() with a SpinWait.SpinUntil() but it doesnt work

让我们从代码开始;

checkedUnlock 是一个 HashSet<ulong>

_hashsetLock是一个对象

lock (_hashsetLock)
    newMap = checkedUnlock.Add(uniqueId);

fun 在 int

SpinWait.SpinUntil(() => Interlocked.CompareExchange(ref fun, 1, 0) == 1);
newMap = checkedUnlock.Add(uniqueId);
fun = 0;

我的理解是 SpinWait 在这种情况下应该像 lock() 一样工作,但是 HashSet 中添加了更多项目,有时它匹配锁,有时有 1 到里面还有 5 项,很明显它不起作用

我的理解有问题吗?

编辑

我试过了,它似乎有效,到目前为止,我的测试显示与 lock() 相同的数字

SpinWait spin = new SpinWait();
while (Interlocked.CompareExchange(ref fun, 1, 0) == 1)
   spin.SpinOnce();

那为什么它可以用这个而不是 SpinWait.SpinUntil()

编辑 #2

小满申请看

在这段代码中,SpinWait.SpinUntil 有时会崩溃(add 会抛出异常)但是当它工作时,计数会不同所以我对这个的预期行为是错误的

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var list = new List<int>();
            var rnd = new Random(42);
            for (var i = 0; i < 1000000; ++i)
                list.Add(rnd.Next(500000));



            object _lock1 = new object();
            var hashset1 = new HashSet<int>();

            int _lock2 = 0;
            var hashset2 = new HashSet<int>();

            int _lock3 = 0;
            var hashset3 = new HashSet<int>();

            Parallel.ForEach(list, item =>

            {
                /******************/
                lock (_lock1)
                    hashset1.Add(item);
                /******************/

                /******************/
                SpinWait.SpinUntil(() => Interlocked.CompareExchange(ref _lock2, 1, 0) == 1);

                hashset2.Add(item);

                _lock2 = 0;
                /******************/

                /******************/
                SpinWait spin = new SpinWait();
                while (Interlocked.CompareExchange(ref _lock3, 1, 0) == 1)
                    spin.SpinOnce();

                hashset3.Add(item);

                _lock3 = 0;
                /******************/

            });


            Console.WriteLine("Lock: {0}", hashset1.Count);
            Console.WriteLine("SpinWaitUntil: {0}", hashset2.Count);
            Console.WriteLine("SpinWait: {0}", hashset3.Count);

            Console.ReadKey();
        }

    }
}

SpinWait.SpinUntil中使用的条件是错误的。

  1. Interlocked.CompareExchangereturns变量原值
  2. SpinWait.SpinUntil 的 MSDN 文档说,条件是

A delegate to be executed over and over until it returns true.

您想旋转直到发生 0 -> 1 转换,所以条件应该是

Interlocked.CompareExchange(ref fun, 1, 0) == 0

在其他线程上对 CompareExchange 的后续调用结果为 1,因此它们将等待 "winner" 线程将 fun 标志恢复为 0。

进一步说明:

  • fun = 0; 应该适用于 x86 架构,但我不确定它在任何地方都是正确的。如果您使用 Interlocked 访问某个字段,则最佳做法是对对该字段的所有访问使用 Interlocked。所以我建议改为Interlocked.Exchange(ref fun, 0)
  • SpinWait 在性能方面很少是一个好的解决方案,因为它可以防止 OS 将旋转线程置于空闲状态。它应该只用于非常短的等待。 (An example of a proper usage)。简单的锁(又名 Monitor.Enter/Exit)或 SemaphoreSlim 通常都可以,或者您可以考虑 ReaderWriterLockSlim,如果 # of reads >> # of writes.