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

时间:2018-08-08 15:59:12

标签: c# multithreading concurrency thread-safety spinwait

让我们从代码开始;

checkedUnlockHashSet<ulong>

_hashsetLock是一个对象

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

vs

fun in 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有时会被炸毁(添加将引发异常),但是当它工作时,计数将有所不同,因此我对此的预期行为是错误的

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

    }
}

1 个答案:

答案 0 :(得分:-1)

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

  1. Interlocked.CompareExchange返回变量的原始值。
  2. SpinWait.SpinUntil的MSDN文档说,条件是
  

要反复执行直到返回true的委托。

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

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

随后在其他线程上对CompareExchange的调用将导致1,因此它们将等待,直到{winer}线程将fun标志恢复为0为止。

更多说明:

  • fun = 0;应该适用于x86架构,但是我不确定它在任何地方都正确。如果使用“互锁”来访问字段,则最佳做法是对所有对该字段的访问都使用“互锁”。因此,我建议改用Interlocked.Exchange(ref fun, 0)
  • SpinWait在性能方面很少是一个好的解决方案,因为它阻止操作系统将旋转线程置于空闲状态。它仅应用于非常短的等待。 (An example of a proper usage)。简单的锁(也称为Monitor.Enter / Exit)或SemaphoreSlim通常可以使用,或者如果读取次数>>写入次数,则可以考虑ReaderWriterLockSlim。