因为在多线程服务器中使用了一个消息队列,该消息队列使用了两把锁控制读写,因此效率损失较大,故探索多生产者多消费者的无锁队列以提升性能 本文引用了CSDN xin_hen的观点和代码并做出了一定调整:https://blog.csdn.net/xin_hen/article/details/108145222
前言
什么是无锁队列
无锁队列一般指的是通过CAS操作来保证队列的线程安全性问题,而不会使得线程陷入到内核,以避免用户态与内核态的切换开销
无锁队列无需用户在外围控制锁来保证队列的线程安全问题,减少因为锁带来的性能开销。
什么是CAS
CAS(Compare and swap, 对比后交换),这是一种所有CPU都支持的原子操作,由于其原子性,可以被用来实现各类无锁数据结构。 其在C++11中的atomic中被支持,因此可以实现跨平台的开发。
1 |
|
compare_exchange_weak
的作用是如果当前变量的值==expected的值,则将当前变量的值改为desired,返回true,否则则返回false。
什么是自旋锁
自旋锁是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
实现
实现思路
在了解基础概念后,就可以尝试这实现无锁队列了: 对于任意一个线程,在头或尾节点不为空后使用compare_exchange_weak将该节点置空,便可以抢占该内存地址(对于其他线程来说,该节点为空,因此无法抢占而自旋),因此可以利用CAS实现另类的加锁。
代码说明
完整代码
1 |
|
重点说明
Push操作
声明两个临时节点t
和node
,其中t
是用来保存队尾指针的临时指针,node
是保存新值的节点。
1 |
|
对队尾指针使用CAS设置为nullptr,形成自旋锁。
1 |
|
如果获取自旋锁成功,则设置尾指针的next节点为输入的元素,
1 |
|
然后解锁尾指针,将tail进行CAS设置为tail->next。
1 |
|
Pop操作
声明两个临时节点h
和h_next
,其中h
是用于保存队首的临时变量,h_next
则是用于保存队首下一个元素的临时变量。
1 |
|
对队首使用CAS设置为nullptr,形成自旋锁,需要注意的是,当队列有数据(h->next != nullptr)且count==0时,则说明push还没完,因此还继续自旋等待(该等待是占用着资源,只是让开时间片)。
1 |
|
如果获取自旋锁成功,则需要判断当前队列是否是空的,如果不为空,则将队首通过CAS设置为h_next,并返回True,否则返回False
1 |
|
验证
验证代码可见https://gitee.com/wangjinchaoXY/Realization/blob/master/Realization/LockFreeLinkedQueueTest.cpp 请自行编译下载。 也可以看 https://gitee.com/JogerQiao/lock-free-queue