某次面试谈到无锁双向链表

面试官让实现无锁双向链表,在不考虑删除的前提下把插入实现了

当时还没开始想具体怎么实现,我就随口先说了脑子里都是浆糊,其实我那会儿是刚准备进行构思,可能被误以为是放弃了挑战。

后来回家以后当然是把问题解决了,问题不大。

并且实现了insert_before和insert_after,从完备性角度看,应该要提供这两个而不是像std的insert(after),有无锁素养的人应该都能很轻松的想到这点。

因为可能会出现{a,b}   

insert_before(b, x)和insert_after(a, y)同时执行 

=> insert_after(before(b), x) 和insert_after(a,y)同时执行

可能性1   insert_after(a,y) -> insert_after(before(b), x) => {a,y,b} ->{a,y,x,b}

可能性2   insert_after(before(b), x) -> insert_after(a,y) => {a,x,b} -> {a,y,x,b}

可能性3   ?=before(b) -> insert_after(a,y) ->insert_after(?, x) -> => {a,y,b} -> {a,x,y,b}

可能性3是违背两个insert本意的。所以不难想到应该分别提供insert_before和insert_after。


那么现在来想想有删除的情况。

其实如果考虑到删除,通用无锁双向链表{a,b,c}就站不住脚了:

当一个线程A正在使用a的数据时,线程B遍历到a并且想要删除a,很显然会造成A对a的读取发生致命错误。

那么好像只能用类似引用计数的方案对a节点加锁,使A读取a时,B无法删除a。

由于lockfree_double_link_list<DataType>的DataType应该是通用,所以不可能在DataType里有引用计数,

那么只能让ListNode<DataType>携带引用计数了,而且很显然的就算不考虑DataType的锁,

Node本身在某线程A遍历并正好读取其值时,另一个线程B就不允许删除这个Node。但是如果每次遍历经过NodeA就需要对NodeA进行加锁,那为什么不直接对整个链表加锁呢?对每个节点加锁显然不能让性能提升(仅有一种情况就是多个线程分别操作链表的某个片段,可以做到互不干涉,但即便如此仍然谈不上有加速作用),那么整个链表的无锁就毫无意义。

所以通用双向无锁链表根本没有存在的理由了。

除非,DataType不通用,或有一套释放体系,或者应用层不持有Node的迭代器

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页