问题描述
|
首先,让我解释一下情况...
我有一个具有2个属性的类:DataA和DataB;不论它们是什么无关紧要,重要的是彼此之间可以相互计算。我处于多线程环境中,希望在必要时(必要时不总是访问两者)来计算DataA / DataB。我的第一个念头是...
public SomeDataObject DataA
{
get
{
if (dataAisAvailable)
{
return dataA;
}
else
{
if (dataBisAvailable)
{
lock (dataACalcLock)
{
// Don\'t want other threads recalculating dataA
if (dataAisAvailable)
{
return dataA;
}
////////////////////////////////
// Calculate dataA from dataB //
////////////////////////////////
dataAisAvailable = true;
return dataA;
}
}
else
{
return null;
}
}
}
}
在这种模式下,假设dataB确实可用,那么到达现场的第一个线程(我们称其为thread1)进入并计算dataA,到目前为止一切都很好……但是,任何一直在等待的线程现在要计算的dataA必须一个一个地访问它...效率不是很高。我想让这些线程等待线程1完成对dataA的计算,然后一切就好像没有锁一样。
也许我对ManualResetEvents还有其他想法,但是我不确定如何才能安全地确保一个线程和一个线程完全通过计算。
我希望我已经清楚地说明了这一点,尽管我不相信自己有。很高兴澄清一下...
编辑:我不好,我在.NET 4.0上。这是在Silverlight中...
解决方法
这听起来很像发明ѭ1的情况(允许多个并发读取器,而一次只能写入一个线程)。
可能看起来像这样(没有正确验证此代码,因此请确保在决定使用该代码时可以按预期工作):
private ReaderWriterLockSlim dataLock = new ReaderWriterLockSlim();
public SomeDataObject DataA
{
get
{
if (dataAisAvailable)
{
return dataA;
}
dataLock.EnterReadLock();
try
{
if (dataBisAvailable)
{
dataLock.EnterUpgradeableReadLock();
try
{
// Don\'t want other threads recalculating dataA
if (dataAisAvailable)
{
return dataA;
}
dataLock.EnterWriteLock();
try
{
////////////////////////////////
// Calculate dataA from dataB //
////////////////////////////////
dataAisAvailable = true;
}
finally
{
dataLock.ExitWriteLock();
}
return dataA;
}
finally
{
dataLock.ExitUpgradeableReadLock();
}
}
else
{
return null;
}
}
finally
{
dataLock.EnterReadLock();
}
}
}
提示/插件:如果您想减少添加的try / final结构的数量,可以将其包装在扩展方法中(如我的博客所述),或者通过将其包装在IDisposable
代理中甚至更清洁(由乔什·佩里(Josh Perry)建议)。
,受到关于我的问题的评论的启发,该评论随后被删除,我签出了Lazy<>
并进行了类似...
static SomeDataObject DefaultData;
private Lazy<SomeDataObject> dataA = new Lazy<SomeDataObject>(() => DefaultData,LazyThreadSafetyMode.ExecutionAndPublication);
private Lazy<SomeDataObject> dataB = new Lazy<SomeDataObject>(() => DefaultData,LazyThreadSafetyMode.ExecutionAndPublication);
public SomeDataObject DataA
{
get
{
return dataA.Value;
}
set
{
dataA = new Lazy<SomeDataObject>(() => value,LazyThreadSafetyMode.ExecutionAndPublication);
dataB = new Lazy<SomeDataObject>(GetDataB,LazyThreadSafetyMode.ExecutionAndPublication);
}
}
public SomeDataObject DataB
{
get
{
return dataB.Value;
}
set
{
dataB = new Lazy<SomeDataObject>(() => value,LazyThreadSafetyMode.ExecutionAndPublication);
dataA = new Lazy<SomeDataObject>(GetDataA,LazyThreadSafetyMode.ExecutionAndPublication);
}
}
private SomeDataObject GetDataA()
{
if (DefaultData == dataB.Value)
{
return null;
}
////////////////////////////////
// Calculate dataA from dataB //
// and return it. //
////////////////////////////////
}
private SomeDataObject GetDataB()
{
if (DefaultData == dataA.Value)
{
return null;
}
////////////////////////////////
// Calculate dataA from dataB //
// and return it. //
////////////////////////////////
}
不幸的是,Silverlight中没有ReaderWriterSlimLock,否则它看起来很有希望。一点基准测试表明,以上内容比我自己编写的内容要快得多(更不用说做我想做的事了)。
,建议:
if (dataAisAvailable)
{
//Wait for AutoResetEvent here,perhaps add a timeout and when it expires,you can return the current dataA,so threads don\'t wait forever.
return dataA;
}
else
{
if (dataBisAvailable)
{
lock (dataACalcLock)
{
// Don\'t want other threads recalculating dataA
if (dataAisAvailable)
{
return dataA;
}
////////////////////////////////
// Calculate dataA from dataB //
////////////////////////////////
dataAisAvailable = true;
//Set AutoResetEvent to signalled so waiting threads can get to DataA.
return dataA;
}
}
,可以肯定的是,您要做的是避免重新计算A和B,同时避免在第一次计算后访问它们时出现锁,是吗?
如果线程读取dataAisAvailable并发现它为true,则没有问题-线程可以使用A。如果线程读取dataAisAvailable并发现它为false,则有问题,它需要获取排他锁以确保dataAisAvailable仍然存在如果为假,则进行计算。我认为,关键部分可以。如果A / B花费了很多时间来计算,这将退回到内核锁,但这只会在线程第一次发现布尔值false时才发生。
我认为您可以避免这种情况,因为布尔值只能从假到真地“单向”,因此您可以通过简单的布尔值检查来摆脱(我认为)。
您的“线程1”将发现dataAisAvailable为false,因此尝试获取该锁。如果成功,它将再次在锁内检查dataAisAvailable,然后计算是否成功。然后,它退出锁,返回dataA。如果“线程2”首先进入,则在线程1中检查dataAisAvailable和线程1获得锁之间,计算dataA并退出锁,然后线程1将进入锁,发现dataAisAvailable现在为true,因此只需退出锁与数据A。
然后,线程2-N将始终将dataAisAvailable视为true并获得A,而根本不尝试获取锁。
Rgds,
马丁