我正在为C ++中的数据缓存编写模板库,其中可以进行并发读取,也可以进行并发写入,但不能用于相同的密钥。可以使用以下环境解释该模式:
这样,如果线程从缓存请求密钥并且不存在,则可以为该唯一密钥启动锁定计算。与此同时,其他线程可以检索或计算其他键的数据,但尝试访问第一个键的线程会被锁定等待。
主要限制因素是:
我已解决的其他限制是:
我不确定每个密钥使用1个互斥锁是实现此目的的正确方法,但我没有找到任何其他实质上不同的方式。
您是否了解其他模式以实现此目的,或者您认为这是一个合适的解决方案吗?我不喜欢有大约100个互斥锁的想法。 (缓存大小约为100个密钥)
答案 0 :(得分:3)
您可以使用互斥锁池而不是为每个资源分配一个互斥锁。在请求读取时,首先检查有问题的插槽。如果它已经标记了互斥锁,则阻止该互斥锁。如果没有,请将互斥锁分配给该插槽并发出信号,将互斥锁从池中取出。一旦互斥锁无信号,请清除插槽并将互斥锁返回池中。
答案 1 :(得分:2)
你想要锁定而你想要等待。因此,某些地方应该有“条件”(在类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()
且{{1}是signal
。 pthread_cond_signal()
以原子方式释放互斥锁并将线程标记为等待条件;线程被唤醒时,会自动重新获取互斥锁。
这意味着每个包装器必须包含一个条件。这体现了您的各种要求:
请注意,当一个线程希望得到一个值并发现某个其他线程已经忙于计算它时,线程最终会锁定全局互斥锁两次:一次在开头,一次在值可用时。一个更复杂的解决方案,每个包装器有一个互斥锁,可以避免第二次锁定,但除非争用非常高,否则我怀疑这是值得的。
关于有多种互斥体:互斥体很便宜。互斥锁基本上是unlock-and-wait
,它只需要用于存储它的四个字节的RAM。谨防Windows术语:在Win32中,我称之为互斥锁被视为“互锁区域”; Win32在调用int
时创建的东西是完全不同的,可以从几个不同的进程访问,并且由于它涉及到内核的往返,所以要昂贵得多。请注意,在Java中,每个对象实例都包含一个互斥锁,而Java开发人员似乎对该主题并不过分冷漠。
答案 2 :(得分:0)
一种更简单的解决方案的可能性是在整个缓存上使用单个读取器/写入器锁。鉴于您知道存在最大条目数(并且它相对较小),听起来像向缓存添加新密钥是一种“罕见”事件。一般逻辑是:
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
我不知道有关使用模式的更多信息,我不能肯定这是否是一个好的解决方案。但是,它非常简单,如果主要使用读取,那么在锁定方面它非常便宜。