C# 试图用SpinWait.spinUtil()替换锁(),但它不起作用
让我们从代码开始C# 试图用SpinWait.spinUtil()替换锁(),但它不起作用,c#,multithreading,concurrency,thread-safety,spinwait,C#,Multithreading,Concurrency,Thread Safety,Spinwait,让我们从代码开始 checkedUnlock是一个HashSet \u hashsetLock是一个对象 lock (_hashsetLock) newMap = checkedUnlock.Add(uniqueId); vs fun在int中 SpinWait.SpinUntil(() => Interlocked.CompareExchange(ref fun, 1, 0) == 1); newMap = checkedUnlock.Add(uniqueId); fun =
checkedUnlock
是一个HashSet
\u hashsetLock
是一个对象
lock (_hashsetLock)
newMap = checkedUnlock.Add(uniqueId);
vs
fun
在int中
SpinWait.SpinUntil(() => Interlocked.CompareExchange(ref fun, 1, 0) == 1);
newMap = checkedUnlock.Add(uniqueId);
fun = 0;
我的理解是,这个场景中的SpinWait
应该像lock()
一样工作,但是HashSet
中添加了更多的项,有时它与lock匹配,有时又多了1到5个项,这表明它显然不起作用
我的理解有缺陷吗
编辑
我尝试了这个,它似乎起了作用,我的测试显示到目前为止与lock()
相同的数字
SpinWait spin = new SpinWait();
while (Interlocked.CompareExchange(ref fun, 1, 0) == 1)
spin.SpinOnce();
那么,为什么它可以使用这个而不能使用SpinWait.spinUtil()
编辑#2
完整的小应用程序来查看
在这段代码中,SpinWait.spinUtil
有时会爆炸(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();
}
}
}
使用系统;
使用System.Collections.Generic;
使用系统线程;
使用System.Threading.Tasks;
命名空间控制台应用程序1
{
班级计划
{
静态void Main(字符串[]参数)
{
var list=新列表();
var rnd=新的随机变量(42);
对于(变量i=0;i<1000000;++i)
列表。添加(rnd.Next(500000));
对象_lock1=新对象();
var hashset1=新HashSet();
int_lock2=0;
var hashset2=新的HashSet();
int_lock3=0;
var hashset3=新的HashSet();
Parallel.ForEach(列表,项=>
{
/******************/
锁(锁1)
hashset1.添加(项);
/******************/
/******************/
SpinWait.SpinUntil(()=>Interlocked.CompareExchange(参考锁2,1,0)==1);
hashset2.添加(项);
_lock2=0;
/******************/
/******************/
SpinWait spin=新的SpinWait();
while(联锁。比较交换(参考锁定3,1,0)==1)
spin.SpinOnce();
hashset3.添加(项);
_lock3=0;
/******************/
});
WriteLine(“Lock:{0}”,hashset1.Count);
WriteLine(“SpinWaitUntil:{0}”,hashset2.Count);
WriteLine(“SpinWait:{0}”,hashset3.Count);
Console.ReadKey();
}
}
}
SpinWait.SpinUtil中使用的条件错误
Interlocked.CompareExchange(ref fun, 1, 0) == 0
在其他线程上调用CompareExchange的后续结果为1,因此它们将等待“winner”线程将fun
标志恢复为0
还有一些评论:
应该适用于x86体系结构,但我不确定它在任何地方都是正确的。如果使用Interlocked访问某个字段,那么最好对该字段的所有访问使用Interlocked。因此,我建议使用fun=0
联锁的.Exchange(ref-fun,0)
- 就性能而言,SpinWait很少是一个好的解决方案,因为它可以防止操作系统将正在旋转的线程置于空闲状态。它只能用于非常短的等待。(). 简单锁(又名监视器,输入/退出)或SimaPraceSLIM一般都会做,或者如果读写的>*,可以考虑Read RealSerkLoSLIM。
锁有什么问题?你看过信号量lim
类了吗?这是一个很好的选择,而且更容易推理。@ckuri在我的代码中,这是一个只添加的场景,并发字典是为读取而优化的,我没有使用的好处it@tigerswithguitars从代码上看,它使用了spinwait、lock和Monitor的混合,有点重,有点清晰,如果您能更详细地了解一下为什么锁
不适用,以及信号量lim
太重的原因,那将很有帮助。似乎用例很重要,这是一个有趣的问题。我使用CompareExchange的方式是模拟锁的正确方式,其他联机示例只需执行一个!=原始值,我改为一个==新值,如果我使用==0,它会/会在几秒钟内爆炸,因为它就像没有锁一样,直接进行交换也不会达到我预期的效果,如果有另一个线程处理代码的这一部分,它就会停止。对于spinwait的使用,可以清楚地确定它应该在很短的时间内使用,添加到hashset的操作应该属于该类别CompareExchange返回原始值、更改前的值(如果有)。CompareExchange返回0表示标志为0,并且在当前调用期间已更改为1。在任何其他情况下,CompareExchange返回1,这意味着“锁被占用”。@Fredou Try。我希望这能让你信服。这个代码示例不做真正的多线程,只需在围绕整个代码的任务中放置一个循环,一个任务将运行X个时间量,然后第二个任务将启动X个时间量,它不会在线程之间切换。但是你的权利,对于spinduntil,compareExchange与SpintWait逻辑的实例相反,这导致了我的困惑