LevelDB为何用单锁保护状态而非读写锁?该设计有何优势?
// State below is protected by mutex_ port::Mutex mutex_; std::atomic<bool> shutting_down_; port::CondVar background_work_finished_signal_ GUARDED_BY(mutex_); MemTable* mem_; MemTable* imm_ GUARDED_BY(mutex_); // Memtable being compacted std::atomic<bool> has_imm_; // So bg thread can detect non-null imm_ WritableFile* logfile_; uint64_t logfile_number_ GUARDED_BY(mutex_); log::Writer* log_; uint32_t seed_ GUARDED_BY(mutex_); // For sampling. // Queue of writers. std::deque<Writer*> writers_ GUARDED_BY(mutex_);
核心原因与方案优势
LevelDB选择普通互斥锁而非读写锁来保护writers_和imm_,本质是由这些对象的操作特性决定的,具体原因和优势如下:
写操作占主导,读写锁的读优化无意义
LevelDB的写路径是高频操作:每次写请求都会进入writers_队列排队,imm_的切换(memtable写满后转成不可变内存表)也是写路径的关键步骤。读写锁的核心优势是在"多读少写"场景下提升并发读性能,但LevelDB中对writers_和imm_的操作几乎都是写操作(添加/移除writer、切换imm_),读操作极少,用读写锁反而会因额外的锁状态判断带来不必要的性能损耗。操作的原子性与顺序性要求严格
对writers_的操作需要严格保证顺序性,确保写请求按提交顺序处理;imm_的切换则需要与memtable更新、后台压缩线程调度强同步。普通互斥锁提供最简单直接的独占访问,能完美保证这些操作的原子性和顺序性,避免读写锁可能带来的复杂同步问题(比如写锁等待时的读阻塞、读锁持有期间写操作饥饿)。避免读写锁的额外开销
读写锁的实现比普通互斥锁复杂得多,需要维护读计数器、处理读写状态切换,这些都会带来额外的CPU开销。LevelDB作为追求极致性能的KV存储,在场景不匹配的情况下,会选择更轻量的普通互斥锁来减少不必要的性能损耗。简化同步逻辑,降低维护成本
LevelDB中已经用mutex_配合条件变量background_work_finished_signal_调度后台线程,统一使用同一个互斥锁可以简化整体同步逻辑,避免引入多种锁类型带来的复杂度,降低代码维护成本。
内容的提问来源于stack exchange,提问作者Jw C

