多线程无锁(lock-free)队列(queue)的实现探讨

前面我实现了一个无锁堆,下面打算实现无锁的 FIFO 队列。

从队列中取出一个节点比较简单,只需要改变头指针,让它指向下一个元素,多线程环境下,使用 cas 操作,可以保证一次只有一个线程成功更改头指针,而更改成功的线程也就是成功取出元素的线程,其他线程失败,重新取就行了。但是要注意到是,由于写线程不会更改头指针,因此头指针不能指向 NULL 来代表队列中没有元素,我们需要在队列中放置一个空节点,代表尾节点。

我定义 Next=0 的节点为尾节点,那么插入节点的时候,只要把原来尾节点的 Next 指针指向新节点,然后更改尾指针就可以了。开始的时候,队列里有一个空节点O(它的内容没有意义),头指针指向O, 执行一次插入操作后,队列中包含 O->A 两个节点,有效内容保存在 A 节点中,读线程的读取流程如下:

1. 获取 Head 指向的节点 O

2. 发现 O 的 Next 指针不为空(如果为空代表队列为空),取 Next 指针指向的 A 节点

3. 取A的值,然后使用 CAS操作修改头指针指向 A

4. 如果 3 成功,那么删除 O,否则从 1 重新开始。

 

但是,写线程的情况就比较复杂了,写线程需要把最后一个节点的 Next 改为新节点,修改尾指针让它指向尾指针2步,这似乎没法使用一个 CAS 解决。看了国外的一些文档,发现了一些比较复杂的方法,那么有没有简单一些的解决方案呢?

我考虑了很久,如果有多个写线程写一个队列的情况下,貌似很难解决,但是我们可以换位思考,为啥写线程需要多线程?无锁队列在什么情况下需要?

最必要的情况下,任务的排队分发,这时候任务的来源很多情况下只有一个(比如一个 socket 线程)。如果规定写线程只有一个,那么事情就简单了,尾指针只有写线程使用,因此直接更改就可以了,尾节点的 Next 也只有写线程会更改,也只需要直接改!

你看,我们已经设计成功了一个单入多出的无锁队列!它在很多情况下应该就够用了,而且它的效率是非常高的。那么一定需要多线程写的情况下,怎么办?

 

队列的特点是什么? FIFO, 先进先出!而在多线程环境下,两个不同线程写入的元素有顺序要求么,没有!

 

实际上,N 个线程写的队列可以这么处理,每个写线程都写自己的独立队列!读线程只要轮询所有队列,对外部来说,它就是一个在保证 FIFO 的,可以多线程读、多线程写的“一个队列”了。其实,我们需要的其实不是“一个队列”而是一个多线程入、多线程出,能保证 FIFO 的容器。

 

代码稍后放出……

推荐阅读:多线程并发访问无锁队列的算法研究

相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页