我有一个方法需要DateTime
并返回标记该季度结束的日期。由于涉及工作日和假日日历的一些复杂性,我想缓存结果以加快后续调用。我正在使用SortedSet<DateTime>
来维护数据缓存,我使用GetViewBetween
方法进行缓存查找,如下所示:
private static SortedSet<DateTime> quarterEndCache = new SortedSet<DateTime>();
public static DateTime GetNextQuarterEndDate(DateTime date)
{
var oneDayLater = date.AddDays(1.0);
var fiveMonthsLater = date.AddMonths(5);
var range = quarterEndCache.GetViewBetween(oneDayLater, fiveMonthsLater);
if (range.Count > 0)
{
return range.Min;
}
// Perform expensive calc here
}
现在我想让我的缓存线程安全。我没有在任何地方使用锁定会在每次查找时产生性能损失,而是在探索新的ImmutableSortedSet<T>
集合,这将允许我完全避免锁定。问题是ImmutableSortedSet<T>
没有方法GetViewBetween
。有没有办法从ImmutableSortedSet<T>
获得类似的功能?
[编辑]
Servy已经说服我使用普通SortedSet<T>
的锁是最简单的解决方案。我会留下问题,因为我有兴趣知道ImmutableSortedSet<T>
能否有效地处理这种情况。
答案 0 :(得分:1)
让我们把问题分成两部分:
如何使用GetViewBetween
获得与ImmutableSortedSet<T>
类似的功能?我建议使用IndexOf
方法。在下面的代码段中,我创建了一个扩展方法GetRangeBetween
,可以完成这项工作。
如何使用数据不可变数据结构实现无锁,线程安全的更新?尽管这不是原始问题,但对此问题仍有一些持怀疑态度的评论。
immutables框架实现了一个完全符合该目的的方法:System.Collections.Immutable.Update<T>(ref T location, Func<T, T> transformer) where T : class;
该方法内部依赖于原子比较/交换操作。如果您想手动执行此操作,您将在下面找到一个替代实现,其行为应该与Immutable.Update相同。
所以这是代码:
public static class ImmutableExtensions
{
public static IEnumerable<T> GetRangeBetween<T>(
this ImmutableSortedSet<T> set, T min, T max)
{
int i = set.IndexOf(min);
if (i < 0) i = ~i;
while (i < set.Count)
{
T x = set[i++];
if (set.KeyComparer.Compare(x, min) >= 0 &&
set.KeyComparer.Compare(x, max) <= 0)
{
yield return x;
}
else
{
break;
}
}
}
public static void LockfreeUpdate<T>(ref T item, Func<T, T> fn)
where T: class
{
T x, y;
do
{
x = item;
y = fn(x);
} while (Interlocked.CompareExchange(ref item, y, x) != x);
}
}
用法:
private static volatile ImmutableSortedSet<DateTime> quarterEndCache =
ImmutableSortedSet<DateTime>.Empty;
private static volatile int counter; // test/verification purpose only
public static DateTime GetNextQuarterEndDate(DateTime date)
{
var oneDayLater = date.AddDays(1.0);
var fiveMonthsLater = date.AddMonths(5);
var range = quarterEndCache.GetRangeBetween(oneDayLater, fiveMonthsLater);
if (range.Any())
{
return range.First();
}
// Perform expensive calc here
// -> Meaningless dummy computation for verification purpose only
long x = Interlocked.Increment(ref counter);
DateTime test = DateTime.FromFileTime(x);
ImmutableExtensions.LockfreeUpdate(
ref quarterEndCache,
c => c.Add(test));
return test;
}
[TestMethod]
public void TestIt()
{
var tasks = Enumerable
.Range(0, 100000)
.Select(x => Task.Factory.StartNew(
() => GetNextQuarterEndDate(DateTime.Now)))
.ToArray();
Task.WaitAll(tasks);
Assert.AreEqual(100000, counter);
}