[英]Design options for a C++ thread-safe object cache
我正在为C ++中的数据缓存编写模板库,其中可以进行并发读取,也可以进行并发写入,但不能用于相同的密钥。 可以使用以下环境解释该模式:
这样,如果线程从缓存请求密钥并且不存在,则可以为该唯一密钥启动锁定计算。 与此同时,其他线程可以检索或计算其他键的数据,但尝试访问第一个键的线程会被锁定等待。
主要限制因素是:
我已解决的其他限制是:
我不确定每个密钥使用1个互斥是实现这个的正确方法,但我没有找到任何其他实质上不同的方式。
您是否了解其他模式以实现此目的,或者您认为这是一个合适的解决方案吗? 我不喜欢有大约100个互斥锁的想法。 (缓存大小约为100键)
您可以使用互斥池而不是为每个资源分配一个互斥锁。 在请求读取时,首先检查有问题的插槽。 如果它已经标记了互斥锁,则阻止该互斥锁。 如果没有,请将互斥锁分配给该插槽并发出信号,将互斥锁从池中取出。 一旦互斥锁未通知,请清除插槽并将互斥锁返回池中。
你想锁定,你想等待。 因此,某些地方应该有“条件”(在类Unix系统上为pthread_cond_t
)。
我建议如下:
当线程希望从缓存中获取值时,它首先获取全局互斥锁。 然后它在地图中查找:
在伪代码中,这看起来像这样:
mutex_t global_mutex hashmap_t map lock(global_mutex) w = map.get(key) if (w == NULL) { w = new Wrapper map.put(key, w) unlock(global_mutex) v = compute_value() lock(global_mutex) w.set(v) signal(w.cond) unlock(global_mutex) return v } else { v = w.get() while (v == NULL) { unlock-and-wait(global_mutex, w.cond) v = w.get() } unlock(global_mutex) return v }
在pthreads
术语中, lock
是pthread_mutex_lock()
, unlock
是pthread_mutex_unlock()
, unlock-and-wait
是pthread_cond_wait()
, signal
是pthread_cond_signal()
。 unlock-and-wait
以原子方式释放互斥锁并将线程标记为等待条件; 线程被唤醒时,会自动重新获取互斥锁。
这意味着每个包装器必须包含一个条件。 这体现了您的各种要求:
请注意,当一个线程希望得到一个值并发现某个其他线程已经忙于计算它时,线程最终会锁定全局互斥锁两次:一次在开头,一次在值可用时。 一个更复杂的解决方案,每个包装器有一个互斥锁,可以避免第二次锁定,但除非争用非常高,否则我怀疑这是值得的。
关于有许多互斥体:互斥体很便宜。 互斥体基本上是一个int
,它的成本只不过是用于存储它的四个字节的RAM。 谨防Windows术语:在Win32中,我称之为互斥锁被视为“互锁区域”; Win32在调用CreateMutex()
时创建的东西是完全不同的,可以从几个不同的进程访问,并且由于它涉及到内核的往返,所以要昂贵得多。 请注意,在Java中,每个对象实例都包含一个互斥锁,而Java开发人员似乎并没有对该主题过于脾气暴躁。
一种更简单的解决方案的可能性是在整个缓存上使用单个读取器/写入器锁。 鉴于您知道存在最大条目数(并且它相对较小),听起来像向缓存添加新密钥是一种“罕见”事件。 一般逻辑是:
acquire read lock
search for key
if found
use the key
else
release read lock
acquire write lock
add key
release write lock
// acquire the read lock again and use it (probably encapsulate in a method)
endif
我不知道有关使用模式的更多信息,我不能肯定这是否是一个很好的解决方案。 但是,它非常简单,如果使用主要是读取,那么在锁定方面它非常便宜。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.