什么是CAS呢?Compare-and-Swap,即比较并替换,也有叫做Compare-and-Set的,比较并设置。
1、比较:读取到了一个值A,在将其更新为B之前,检查原值是否仍为A(未被其他线程改动)。
2、设置:如果是,将A更新为B,结束。[1]如果不是,则什么都不做。
上面的两步操作是原子性的,可以简单地理解为瞬间完成,在CPU看来就是一步操作。
有了CAS,就可以实现一个乐观锁。
class Queue
{
private:
struct qnode<ElemType> *volatile _head = NULL; // 随着pop后指向的位置是不一样的, head不是固定的
struct qnode<ElemType> *volatile _tail = NULL;
public:
Queue()
{
_head = _tail = new qnode<ElemType>;
_head->_next = NULL;
_tail->_next = NULL;
// printf("Queue _head:%p\n", _head);
}
// printf("Queue _head:%p\n", _head);
}
void push(const ElemType &e)
{
struct qnode<ElemType> *p = new qnode<ElemType>;
// printf("push head:%p, p:%p\n", _head, p);
p->_next = NULL;
p->_data = e;
struct qnode<ElemType> *t = _tail;
struct qnode<ElemType> *old_t = _tail;
int count = 0;
do
{
while (t->_next != NULL) // 非空的时候要去更新 t->_next
t = t->_next; // 找到最后的节点
if (count++ >= 1)
{
// printf("push count:%d, t->_next:%p\n", count, t->_next);
}
// 将null换为p即是插入的节点
} while (!__sync_bool_compare_and_swap(&t->_next, NULL, p));
// 将最后的节点_tail更换为p节点
__sync_bool_compare_and_swap(&_tail, old_t, p);
}
bool pop(ElemType &e)
{
struct qnode<ElemType> *p = NULL;
struct qnode<ElemType> *np = NULL;
int count = 0;
do
{
p = _head; // 头节点,不真正存储数据
// np = _head->_next;
if (p->_next == NULL) // 首元节点为空,则返回
{
// printf("return false");
return false;
}
} while (!__sync_bool_compare_and_swap(&_head, p, p->_next));
e = p->_data;
// printf("pop p:%p\n", p);
// delete p; // 因为我们已经将头部节点换成了p->_next, 所以可以释放掉
return true;
}
在push函数中需要两个操作,一个是改变tail一个是改变tail->next,所以需要两个__sync_bool_compare_and_swap。__sync_bool_compare_and_swap(&_tail, old_t, p)不用放在while循环里是因为,这里不需8要保证每个线程tail的值正确,因为可以通过不断的t = t->_next找到插入的位置。
在pop函数中,cas操作的只能是head而不能是head->next, 因为此时pop函数应该改成
bool pop(ElemType &e)
{
struct qnode<ElemType> *p = NULL;
struct qnode<ElemType> *np = NULL;
int count = 0;
do
{
np = _head->_next;
if (np == NULL) // 首元节点为空,则返回
{
// printf("return false");
return false;
}
} while (!__sync_bool_compare_and_swap(&(_head->_next), np, np->_next));
// printf("pop p:%p\n", p);
// delete p; // 因为我们已经将头部节点换成了p->_next, 所以可以释放掉
return true;
}
而这时不能能保证np->_next一定存在:,如下图所示,A地址在线程1中为头结点,而在线程2中,A地址经过delete之后可能分配给一个新的节点,并且这个节点的next为null,所以此时线程1中np=A->next=NULL,cas中的np->next报错
按顺序插入
while (prev->next != NULL && prev->next->item < node->item)保证了pre一定是node的前继节点。
void insert(Node *prev, Node *node) {
while (true) {
while (prev->next != NULL && prev->next->item < node->item) {
prev = prev->next;
}
node->next = prev->next;
if (__sync_compare_and_swap(&prev->next, node->next, node)) {
return;
}
}
}