试图用 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中使用的条件是错误的。
- Interlocked.CompareExchangereturns变量原值
- 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.
让我们从代码开始;
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中使用的条件是错误的。
- Interlocked.CompareExchangereturns变量原值
- 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.