with cache_lock.reader_lock: x = cache_fetch_under_rlock(params) if x is not None: return x # cache miss; must populate with cache_lock.writer_lock: x = cache_fetch_under_rlock(params) if x is not None: # oops, we didn't need to write, oh well return x x = do_the_expensive_thing(params) cache_write_under_wlock(params, x) return x # do_the_expensive_thing may repeat this pattern with a different lock, # if it has its own cache or pipelined single source of truth. This # is a nestable, generic pattern for maximizing cache throughput # while provably not duplicating the expensive thing. It has the # disadvantage of holding the cache lock for the entire Expensive Thing # but this is often appropriate; if it is worse to unnecessarily hold # the writer lock than it is to duplicate work, however... with cache_lock.reader_lock: x = cache_fetch_under_rlock(params) if x is not None: return x z = do_the_expensive_thing(params) with cache_lock.writer_lock: x = cache_fetch_under_rlock(params) if x is not None: # oops return x # it is more GC-memory-efficient to throw z away; # under garbage collection, prefer old objects cache_write_under_wlock(params, z) return z