.NET 锁定还是 ConcurrentDictionary?

Posted

技术标签:

【中文标题】.NET 锁定还是 ConcurrentDictionary?【英文标题】:.NET locking or ConcurrentDictionary? 【发布时间】:2021-10-04 10:20:29 【问题描述】:

我正在编写文件缓存之类的东西,我正在争论使用锁还是 ConcurrentDictionary。如果多个线程要求一个键,那么如果两个线程尝试写入普通字典就会出现问题,所以我尝试了 ConcurrentDictionary。现在有一个次要问题,当每个线程试图获取文件时,如何防止文件被读取两次(或更多次)。我添加了示例代码来解释我的意思。

这是一个使用锁和字典的版本

class Program

    private static object locking = new object();
    private static Dictionary<string, byte[]> cache;
    
    static void Main(string[] args)
    
        cache = new Dictionary<string, byte[]>();
        
        Task.Run(() =>
        
            AddToCache("largefile", "largefile.bin");
        );

        Task.Run(() =>
        
            AddToCache("largefile", "largefile.bin");
        );
    
    
    static byte[] AddToCache(string key, string filename)
    
        lock(locking)
        
            if (cache.TryGetValue(key, out byte[] data))
            
                Console.WriteLine("Found in cache");
                return data;
            

            Console.WriteLine("Reading file into cache");
            data = File.ReadAllBytes(filename);
            cache[key] = data;
            return data;
        
    

这个版本符合预期,它可以保护字典免受多线程的影响,并且只读取一次大文件。

这是使用 ConcurrentDictionary 的第二个版本:

class Program

    private static ConcurrentDictionary<string, byte[]> cache;

    static void Main(string[] args)
    
        cache = new ConcurrentDictionary<string, byte[]>();

        Task.Run(() =>
        
            AddToCache("largefile", "largefile.bin");
        );

        Task.Run(() =>
        
            AddToCache("largefile", "largefile.bin");
        );
    

    static byte[] AddToCache(string key, string filename)
    
        return cache.GetOrAdd(key, (s) => 
        
            Console.WriteLine("Reading file into cache");
            return File.ReadAllBytes(filename); 
        );
    

此版本保护字典,但它两次读取大文件,这不是必需的。我认为我在这里做错了,但不熟悉 GetOrAdd 我不确定是什么。

第一个版本看起来不错,但它是真实代码的缩减版本,并且锁会锁定很多代码。第二个版本看起来更简单,但不会阻止多次读取文件。有没有办法做到这一点,而锁不会阻塞大量代码,或者这是唯一的答案?

【问题讨论】:

【参考方案1】:

常见的技巧是使用Lazy 作为ConcurrentDictionary 中的值,这样您就可以使添加部分GetOrAdd 线程安全。在你的情况下,它看起来像这样:

private static ConcurrentDictionary<string, Lazy<byte[]>> cache;

static byte[] AddToCache(string key, string filename) => cache
        .GetOrAdd(key, (s) =>
            new Lazy<byte[]>(() =>
            
                Console.WriteLine("Reading file into cache");
                return File.ReadAllBytes(filename);
            ))
        .Value;

这种方法的缺点可能是值函数的延迟执行,但由于您已经包装了字典访问,因此对您来说应该不是问题。

【讨论】:

以上是关于.NET 锁定还是 ConcurrentDictionary?的主要内容,如果未能解决你的问题,请参考以下文章

PHP检查文件是不是被flock()锁定?

SQL Server 表隔离级别和锁定问题

如何锁定记录和更新自己锁定的记录。 (Asp.Net 和 ADS)

在 asp.net 中锁定缓存的最佳方法是啥?

.NET:锁定 ToolBarButton 而无需专门化该类

.NET - 字典锁定与 ConcurrentDictionary