.NET 锁定还是 ConcurrentDictionary?
.NET locking or ConcurrentDictionary?
我正在编写类似文件缓存的东西,我正在考虑是使用锁还是使用 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 我不确定是什么。
第一个版本看起来不错,但它是真实代码的精简版,锁会锁定很多代码。第二个版本看起来更简单,但不会阻止文件的多次读取。有没有一种方法可以在不阻塞大量代码的情况下做到这一点,或者这是唯一的答案?
常用技巧是使用 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;
这种方法的缺点可能是价值函数的延迟执行,但由于您已经包装了字典访问,所以这对您来说应该不是问题。
我正在编写类似文件缓存的东西,我正在考虑是使用锁还是使用 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 我不确定是什么。
第一个版本看起来不错,但它是真实代码的精简版,锁会锁定很多代码。第二个版本看起来更简单,但不会阻止文件的多次读取。有没有一种方法可以在不阻塞大量代码的情况下做到这一点,或者这是唯一的答案?
常用技巧是使用 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;
这种方法的缺点可能是价值函数的延迟执行,但由于您已经包装了字典访问,所以这对您来说应该不是问题。