Memcached的锁策略

2022-04-13 00:00:00 线程 对象 死锁 链表 锁住

早先的时候看到有人问memcached在并发下的锁策略:“每次访问都要加锁吗?是否会降低效率?”,硕爷的答复是RTFSC。为此,我试着花了一两天的时间读了memcached加锁相关的代码。

memcached的锁分为几类:内存池本身的malloc/free锁,LRU链表操作的锁(每个slabclass一个),hashmap的桶锁(item lock,每个桶一个),背景线程的锁(lru crawler lock),bookkeeping用的锁(stat lock)等等。除了后两类没有依赖关系,前三种锁必须控制加锁顺序,以防出现死锁。

memcached的item对象是引用计数+侵入式链表设计,同一个对象处在多个链表中,这就造成同一个对象的数据成员被多个互斥锁保护。对象必须先分配内存,链入LRU链表,再链入hashmap,销毁的时候更复杂一些:由于引用计数,对象销毁的时候需要考虑一些边界情况。比如一个用户拿到了对象,增加了引用技术并释放了锁,此时另一个线程通过LRU删除了这一item,这时候对象既不在open list也不再close list中,这也是为什么要把LRU锁和内存池锁拆分开来的原因之一。


在memcached的运行中,大致有如下几类情况:

get/gets指令:只锁住item lock

update/gat指令:先锁住item lock,再锁住lru链表。

set/delete指令:锁住item和lru链表,不一定锁住全局allocator锁。

背景线程:lru crawler等线程具有较低的任务优先级,它会锁住lru链表以后再trylock一次item lock,如果失败就放弃一轮迭代,这种方法不会造成死锁。

此外还有很多情况,不一一列举了。


后谈一下读代码本身的经过。在我看来,硕爷的话固然不错,但遇到一个问题立刻就看源码未必是好的学习策略,尤其是新手上路的时候,恐怕应该先问问自己是否有了足够的知识储备。读懂memcached的前置知识条件如下:

基础知识方面:了解锁的基本常识,知道死锁的四个条件。知道基本的slab,LRU,HashMap数据结构;有资源管理的基本概念,至少要了解引用计数,知道C++的做法是怎样的,以及C语言的workaround是怎样的。

从实操角度:先读文档,再读代码。memcached的设计思路在doc/下有所阐释。要静心下来分析,拿出纸来做笔记,写思路图。知道怎么提出猜想,以及改代码来验证猜想。

memcached是C语言写成,读的时候用cscope和vim,加上纸笔就足够了(我读的时候大概画了五页纸的草稿,如果用脑子记,难度还是挺高的)。此外程序中预留了MEMCACHED_*宏,可以通过改写后重新编译来打印输出,有助于理解函数的调用栈。memcached支持文本协议,运行程序后,用ipython或netcat与进程交互,编写简短的脚本来辅助实验,效率比用gdb无脑单步要高。

来源 https://zhuanlan.zhihu.com/p/32273530

相关文章