使用ImmutableSortedSet <t>作为线程安全缓存</t>

时间:2014-12-04 19:52:43

标签: c# caching immutability sortedset

我有一个方法需要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>能否有效地处理这种情况。

1 个答案:

答案 0 :(得分:1)

让我们把问题分成两部分:

  1. 如何使用GetViewBetween获得与ImmutableSortedSet<T>类似的功能?我建议使用IndexOf方法。在下面的代码段中,我创建了一个扩展方法GetRangeBetween,可以完成这项工作。

  2. 如何使用数据不可变数据结构实现无锁,线程安全的更新?尽管这不是原始问题,但对此问题仍有一些持怀疑态度的评论。 immutables框架实现了一个完全符合该目的的方法:System.Collections.Immutable.Update<T>(ref T location, Func<T, T> transformer) where T : class;该方法内部依赖于原子比较/交换操作。如果您想手动执行此操作,您将在下面找到一个替代实现,其行为应该与Immutable.Update相同。

  3. 所以这是代码:

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