/* atomic 原子操作类
load是读
store是存
*/
#include <atomic>
#include <chrono>
#include <ratio>
#include <thread>
template <typename T>
class LockFreeQueue {
private:
struct Node {
T value;
Node* next; // 用next 起名,类型是自身的指针,说明这是一个链表
Node(const T& val) : value(val), next(nullptr){
}
};
std::atomic<Node*> head;
std::atomic<Node*> tail;
public:
LockFreeQueue() : head(new Node(T())), tail(head.load()){ // 一开始 tail指向head
// head 指向 T() 相当于队列的参考标志,不存储实际数据
}
~LockFreeQueue(){
while(head.load()){ // 这里是一直从头部读取,只到没有
Node* tmp = head.load()->next;
delete head.load(); //释放掉头部元素的内存
head.store(tmp);
}
}
void push(const T& val) {
Node* node = new Node(val); // 新加入一个元素 node,为其实例化
Node* prev = tail.exchange(node); // prev 指向 之前的tail,新的tail指向node ;exchange原子互换 tail换成node了,返回之前的 tail
prev->next = node; // prev 先是指向 之前的tail prev-next指向 node 让整体链表连接在一起
}
bool pop(T& val) {
Node* oldHead = head.load(); // 从头部读取一个元素,用oldHead指向它
while (true) {
Node* oldTail = tail.load(); // 从尾部读取一个元素,用oldTail指向它
Node* oldNext = oldHead->next; // 用 oldNext 指向oldHead的next指针
if(oldHead == head.load()){ // 这里是为了确保oldHead没有被修改,或者被其它线程拿走了
if(oldHead == oldTail) { // 头尾相等
if (oldNext == nullptr) { // 头尾相等,并且没有下一个元素时
return false;
}
/* 函数原型:bool compare_exchange_strong(_Ty& _Expected, const _Ty _Desired)
expected预期 desired渴望的,意思可以用伪码表达: 调用者 = (调用者 == _Expected) ? _Desired : _Expected; 而且过程是原子操作
*/
tail.compare_exchange_strong(oldTail, oldNext); // 让tail定位到正确的位置(因为其它线程的pop可能导致tail位置在当前线程中此时的值失真)
} else {
val = oldNext->value; // 相当于队头被pop出去了
// 如果head与oldHead相等,就让head指向oldNext, 否则指向oldHead
if(head.compare_exchange_strong(oldHead, oldNext)) {
delete oldHead; // 因为oldHead已经要被取出,所以这里销毁它
return true;
}
}
}
}
}
};
该实现中,Node结构体代表队列中的节点,包含一个value成员变量和一个next指针。
LockFreeQueue类使用std::atomic<Node*>类型的head和tail成员变量分别表示队列的头和尾,
初始化时都指向一个空节点,即head和tail指向同一个节点。
push操作直接在tail处插入新节点,pop操作则使用compare_exchange_strong方法进行原子操作,如果操作成功,则返回true,
否则继续执行循环。
需要注意的是,在使用无锁队列时需要特别小心,避免出现ABA问题和数据竞争等问题,因此建议在实现无锁队列时充分测试和验证。
测试程序, 可设定不同的情况进行测试,从而更好认识“无锁队列”
#include <iostream>
#include <thread>
#include <functional>
LockFreeQueue<int> queue;
void producer() {
for (int i=0; i < 1000000; ++i){
queue.push(i);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void producer2() {
for (int i=100; i < 200; ++i){
queue.push(i);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void consumer(int id) {
int val = 0;
while (true) {
if (queue.pop(val)) {
std::cout << id << " consume " << val << std::endl;
} else {
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void consumer2(int id) {
int val = 0;
while (true) {
if (queue.pop(val)) {
std::cout << id << " consume " << val << std::endl;
} else {
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main() {
std::thread p1(producer);
//std::thread p2(producer2);
std::thread c1(std::bind(consumer, 1));
std::thread c2(std::bind(consumer2, 2));
p1.join();
//p2.join();
c1.join();
c2.join();
return 0;
}