目录
CAS
CAS(Compare and Swap,比较并交换)是一种并发算法,可以用于多线程环境中实现无锁的同步操作。
在多线程环境中,多个线程同时访问共享的数据,如果不采取措施进行同步,可能会导致数据不一致或者竞争条件的问题。传统的解决方案是使用锁机制,但是锁机制会引入线程的阻塞和唤醒操作,影响性能。
CAS是一种无锁的同步方式,它通过原子操作实现数据的同步。CAS涉及三个操作数:内存位置(V)、旧的预期值(A)和新的值(B)。通过比较当前内存位置的值与旧的预期值是否相等来判断数据是否被改变,如果相等,则用新的值更新数据;如果不相等,则表示数据已经被其他线程改变,当前线程重新尝试。
CAS是一种乐观锁的实现方式,它不需要线程阻塞和唤醒操作,因此在并发性能方面有较高的优势。但是CAS也存在一些问题,例如ABA问题和循环时间长的问题,需要针对具体情况进行处理。
ABA问题
ABA问题指的是在CAS操作中,如果一个值从A变为B,然后再变回A,那么在此期间可能有其他线程进行了一些操作,导致CAS操作误认为该值没有被修改过,从而产生错误的结果。
例子1:
线程A读取一个共享变量的值为A,然后线程B修改该共享变量的值为B,最后又恢复为A,而线程A此时执行CAS操作,会发现该共享变量的值仍然是A,于是CAS操作成功,但实际上该共享变量已经发生了变化。
例子2:
假设有两个线程,A和B,现在他们获取了一个指针pApple,这个指针指向了数据w,假设B先执行,他将指针pApple所指向的数据w进行了释放,线程重新生成的时候,恰好生成的内存也在w所在的位置,并写入了内存。接下来线程A就会继续执行CAS操作,然后访问野指针pApple是成功的【因为指针pApple所指向的仍然是数据w的位置】
为了解决ABA问题,可以引入版本号或标记来跟踪共享变量的变化,每次修改共享变量时都将版本号递增,从而让CAS操作对版本号进行比较来判断共享变量是否被修改过。这样做可以有效地避免ABA问题的发生。
简单的无锁线程安全列表
#include<iostream>
#include<thread>
#include<atomic>
#include<vector>
using namespace std;
class ThreadSafe_List
{
protected:
struct Node
{
int value;
Node* next;
Node(int data,Node* next):value(data),next(next){}
};
atomic<Node*> List_Head;//表头
public:
void pushHead(int data)//头部插入
{
//函数首先创建一个新节点,然后将表头节点赋给一个旧的表头节点指针。
Node* oldHead = List_Head;
Node* newNode = new Node(data,oldHead);
//接下来,使用compare_exchange_weak方法将旧的表头节点指针与新节点的指针交换。
//如果表头节点指针没有被其他线程修改,则交换成功,新节点将成为新的表头节点。
//若交换失败,将重新将新节点的下一个节点指针指向旧的表头节点,然后再次尝试交换。
//这个过程会一直重复,直到交换成功为止。这样可以确保在多线程环境下,只有一个线程能够成功地将新节点插入到表头位置,保证线程安全性。
while (!List_Head.compare_exchange_weak(oldHead,newNode))
{
newNode->next = oldHead;
}
}
void printList()
{
for (Node* itor=List_Head;itor!=nullptr;itor=itor->next)
{
cout << itor->value<<endl;
}
}
~ThreadSafe_List()
{
Node* itor;
while (itor=List_Head)
{
List_Head = itor->next;
delete itor;
}
}
};
int main()
{
vector<thread> t;
ThreadSafe_List test;
for (int i = 0; i < 500; i++)
{
t.push_back(thread(&ThreadSafe_List::pushHead, &test,i));
}
for (auto& v:t)
{
v.join();
}
test.printList();
return 0;
}