【问题标题】:Conditional thread locking条件线程锁定
【发布时间】:2013-02-03 12:30:23
【问题描述】:

场景

我想下载资源。我不希望资源被多次下载。如果线程 a 下载资源 1 它应该被缓存,并且线程 b 应该等待并使用缓存的资源 1 如果它试图下载资源 1同时。如果线程c要下载资源2,应该不受线程ab的影响。

尝试

我已经尝试实现以下场景:

using System;
using System.Collections.Generic;
using System.Threading;

namespace ConsoleApplication1
{
    class ConditionalThreadLockingProgram
    {
        private static readonly object _lockObject = new object();
        private static readonly Dictionary<int, string> Locks =
            new Dictionary<int, string>();
        private static readonly Dictionary<int, string> Resources =
            new Dictionary<int, string>();

        public static string GetLock(int resourceId)
        {
            lock (_lockObject)
            {
                if (Locks.ContainsKey(resourceId))
                {
                    return Locks[resourceId];
                }
                return Locks[resourceId] = string.Format(
                    "Lock #{0}",
                    resourceId
                );
            }
        }

        public static void FetchResource(object resourceIdObject)
        {
            var resourceId = (int)resourceIdObject;
            var currentLock = GetLock(resourceId);
            lock (currentLock)
            {
                if (Resources.ContainsKey(resourceId))
                {
                    Console.WriteLine(
                        "Thread {0} got cached: {1}",
                        Thread.CurrentThread.Name,
                        Resources[resourceId]
                    );
                    return;
                }
                Thread.Sleep(2000);
                Console.WriteLine(
                    "Thread {0} downloaded: {1}",
                    Thread.CurrentThread.Name,
                    Resources[resourceId] = string.Format(
                        "Resource #{0}",
                        resourceId
                    )
                );
            }
        }

        static void Main(string[] args)
        {
            new Thread(FetchResource) { Name = "a" }.Start(1);
            new Thread(FetchResource) { Name = "b" }.Start(1);
            new Thread(FetchResource) { Name = "c" }.Start(2);
            Console.ReadLine();
        }
    }
}

问题

有效吗?有什么问题吗?

【问题讨论】:

  • 是的,我认为它有效。但我对多线程非常陌生。
  • 我可能会建议使用字典的 SyncRoot 而不是创建自己的锁。 GetLock 将使用 (((IDictionary)Locks).SyncRoot),FetchResouce 将使用 (((IDictionary)Resources).SyncRoot)
  • 如果您正在寻找有关工作代码的反馈,将您的问题发布到codereview.stackexchange.com 可能更合适。
  • moncadad:谢谢您的建议。有人说使用SyncRoot属性可能很危险:stackoverflow.com/questions/327654/…
  • dgvid:感谢您告诉我有关 codereview.stackexchange.com 的信息。以前从未使用过该网站。我觉得这个问题是关于在同一个方法中使用不同的锁对象的概念。我觉得这是一个笼统的问题,需要一个例子来说明。

标签: c# multithreading thread-safety


【解决方案1】:

C# 现在包含 LazyConcurrent CollectionsMemoryCache - 为 MemoryCache 添加对 System.Runtime.Caching 的引用。

这就是我要做的 - 不需要额外的锁定,并且 Lazy 实现会处理竞争条件。

/// <summary>
/// Summary description for ResourceFactory
/// </summary>
public static class ResourceFactory
{
    private const string _cacheKeyFormat = "AppResource[{0}]";

    private static readonly ObjectCache _cache = MemoryCache.Default;

    private static readonly CacheItemPolicy _policy = new CacheItemPolicy() 
    { 
        SlidingExpiration = TimeSpan.FromMinutes(Int32.Parse(ConfigurationManager.AppSettings["AppResourceTimeout"] ?? "20")), 
        RemovedCallback = new CacheEntryRemovedCallback(AppResourceRemovedCallback) 
    };

    private static void AppResourceRemovedCallback(CacheEntryRemovedArguments args)
    {
        // item was removed from cache
    }

    #region Extensions to make ObjectCache work with Lazy

    public static TValue GetOrAdd<TKey, TValue>(this ObjectCache @this, TKey key, Func<TKey, TValue> valueFactory, CacheItemPolicy policy)
    {
        Lazy<TValue> lazy = new Lazy<TValue>(() => valueFactory(key), true);
        return ((Lazy<TValue>)@this.AddOrGetExisting(key.ToString(), lazy, policy) ?? lazy).Value;
    }

    public static TValue GetOrAdd<TKey, TParam1, TValue>(this ObjectCache @this, TKey key, TParam1 param1, Func<TKey, TParam1, TValue> valueFactory, CacheItemPolicy policy)
    {
        Lazy<TValue> lazy = new Lazy<TValue>(() => valueFactory(key, param1), true);
        return ((Lazy<TValue>)@this.AddOrGetExisting(key.ToString(), lazy, policy) ?? lazy).Value;
    }

    #endregion


    public static AppResourceEntity GetResourceById(int resourceId)
    {
        #region sanity checks

        if (resourceId < 0) throw new ArgumentException("Invalid parameter", "resourceId");

        #endregion

        string key = string.Format(_cacheKeyFormat, resourceId);

        AppResourceEntity resource = _cache.GetOrAdd(
            key, 
            resourceId, 
            (k, r) =>
            {
                return AppResourceDataLayer.GetResourceById(r);
            }, 
            _policy
        );

        return resource;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-11-04
    • 1970-01-01
    • 2015-07-05
    • 2018-12-24
    • 2017-05-17
    • 1970-01-01
    • 1970-01-01
    • 2021-01-23
    相关资源
    最近更新 更多