我想知道缓存的锁定方案是否比简单的锁更好:
Mutex lock;
get(key) {
LockGuard(lock);
if (cache.has(key)) {
return cache[key];
} else {
data = remoteclient.getslow();
cache[key] = data;
return data;
}
}
假设您有很多相同的请求,那么您每次都会序列化对get()的访问。使用ReadWriter锁可以更聪明地做些什么吗?
即。如果你做了类似的事情怎么办?
ReadersWritersLock lock;
get(key) {
{
ReadLockGuard(lock);
if (cache.has(key)) {
return cache[key];
}
}
WriteLockGuard(lock);
data = remoteclient.getslow();
cache[key] = data;
return data;
}
}
现在,这将允许多个用户在缓存命中的情况下同时获取()。但是,如果两个用户在同一时间到达第一个get(),他们可能会尝试进入代码的第二部分来获取数据。这看起来是个好主意吗?
优化此类代码的其他任何想法?
答案 0 :(得分:2)
我不喜欢发布的代码的一件事是,在两个片段中,调用
remoteclient.getslow();
缓存被锁定时调用。如果remoteclient.getslow()实际上可能需要很长时间才能返回(如名称所示),那么任何其他尝试访问缓存的线程最终都会被阻塞(即直到getslow()返回并且正在调用它的线程释放锁定... ...即使他们只对缓存中已存在的无关数据感兴趣!
为了避免这种情况,我会在LockGuard的范围之外调用remoteclient.getslow()(即在缓存解锁时)。然后,在remoteclient.getslow()返回结果之后,我将重新锁定缓存并使用检索到的值更新缓存。这样,缓存永远不会被锁定。
(当然这样做会打开多个线程为同一个数据项调用remoteclient.getslow()的可能性,如果他们都决定在大约同一时间需要相同的数据......但是是一个可接受的副作用。或者如果没有,你可以设计一种机制来指示特定的缓存值正在被检索的过程中,让其他线程阻塞直到检索完成...如果值得额外的复杂性对你来说。这可能需要条件变量等来正确完成)
答案 1 :(得分:1)
您的伪代码有正确的想法,但它有竞争条件。
只要ReadLockGuard超出范围,就会失去锁定,这意味着在WriteLockGuard有时间获取锁定之前,另一个线程可以修改数据结构。
如果您的读者/作者锁实现支持可升级锁,那么您应该使用它。否则,在获取写入锁之后,您需要仔细检查缓存,以防它在“reader”版本和“writer”获取之间填充。
答案 2 :(得分:1)
两个线程可能会进入get()的'writing'部分,但可能不太可能。如果您担心额外的getslow()调用的惩罚,您可以再次检查编写器锁。
ReadersWritersLock lock;
get(key) {
{
ReadLockGuard(lock);
if (cache.has(key)) {
return cache[key];
}
}
WriteLockGuard(lock);
if (cache.has(key) == false) {
data = remoteclient.getslow();
cache[key] = data;
return data;
}
}