面试题答案
一键面试1. ABA问题阐述
ABA问题指的是在无锁编程中,一个值从A变成B,再变回A,而在这个过程中,其他线程可能会因为只看到了开始和结束的值都是A,就误以为这个值没有发生过变化,但实际上它经历了变化。这可能导致一些隐藏的错误,因为中间状态的变化可能已经影响了程序的逻辑。
2. 多核环境下无锁数据结构应用场景(以无锁链表为例)
假设有一个无锁链表,线程1想要删除链表中的节点A。线程1首先获取到节点A的引用,然后检查它的下一个节点是否还是预期的节点B(假设节点A的下一个节点是B)。就在线程1准备删除节点A之前,线程2介入,它删除了节点A,然后又创建了一个新的节点A并插入到链表相同位置,且新节点A的下一个节点也是B。此时线程1继续执行删除操作,它看到节点A的下一个节点还是B,就以为链表状态没有改变,正常执行删除操作,但实际上它删除的是新创建的节点A,这就破坏了链表的结构。
3. 解决ABA问题的方法及原理
- 使用版本号(或时间戳):
- 原理:给每个数据加上一个版本号,每当数据发生变化,版本号就递增。当一个线程想要修改数据时,它不仅要检查数据的值,还要检查版本号是否与预期一致。如果版本号不一致,说明数据在这期间发生了变化,线程需要重新获取数据和版本号,再尝试操作。
- 使用原子的指针和计数器联合体(如
std::atomic< std::pair<Node*, int> >
在C++ 中,C语言可类似模拟):- 原理:将指针和一个计数器组合在一起,每次指针发生变化(比如节点被删除再重新创建),计数器就增加。线程在操作时同时检查指针和计数器,只有两者都符合预期才进行操作。
4. C语言代码示意(使用版本号方式)
#include <stdio.h>
#include <stdatomic.h>
// 定义节点结构
typedef struct Node {
int value;
atomic_int version;
struct Node* next;
} Node;
// 创建新节点
Node* createNode(int val) {
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->value = val;
newNode->version = 0;
newNode->next = NULL;
return newNode;
}
// 尝试删除节点
int tryDelete(Node** head, Node* target) {
Node* current = *head;
while (current != NULL) {
if (current == target) {
int expectedVersion = atomic_load(¤t->version);
// 尝试增加版本号并删除节点
if (atomic_compare_exchange_strong(¤t->version, &expectedVersion, expectedVersion + 1)) {
if (current == *head) {
*head = current->next;
} else {
Node* prev = *head;
while (prev->next != current) {
prev = prev->next;
}
prev->next = current->next;
}
free(current);
return 1;
} else {
return 0;
}
}
current = current->next;
}
return 0;
}