一、原子操作概念
1. 什么是原子操作?
原子操作 = 不可被中断的操作就像原子是不可分割的最小单位一样。
CPU 执行原子操作时:
- 一步完成
- 执行期间不会被线程切换打断
- 要么完全执行完,要么完全没执行
- 不会出现 "执行一半被抢走 CPU" 的情况
2. 哪些是原子操作?
- 简单赋值:
a = 100; - 简单读取:
int b = a;
3. 哪些 不是 原子操作?
这些操作会被线程打断:
a = b + 100;(读 → 算 → 写,三步)i++;(读 → 加 → 写)- 链表插入、链表删除
printf()不是原子操作(内部复杂)malloc/free不是原子操作
只要不是一步 CPU 指令,就不是原子操作!
二、为什么非原子操作必须加锁?
因为:非原子操作 = 多步指令 = 可能执行到一半被抢走 CPU一旦被抢走,数据就会错乱、崩溃、覆盖、丢失。
三、用两个示例代码讲清楚
我们以链表头插法为例:
newNode->next = head;
head = newNode;
这两行代码 不是原子操作!必须连续执行,不能被打断。
示例代码 1:不加锁(错误!)
void* producer(void *arg)
{
while(1)
{
// 创建节点(原子与否不重要)
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = rand()%100;
// ==========================
// 这两行不是原子操作!!!
newNode->next = head;
head = newNode;
// ==========================
printf("+++product: %d\n", newNode->data);
sleep(rand()%3);
}
return NULL;
}
错误原因:
newNode->next = head; 和 head = newNode;不是原子操作!
两个线程同时执行时,可能:
-
线程 A 刚执行完第一行
-
CPU 被抢走
-
线程 B 也修改 head
-
回来后线程 A 继续执行 → 链表断了、节点丢了、程序崩溃!
结论:
多线程同时操作链表,不加锁 = 程序必定崩溃 / 数据错乱
示例代码 2:加锁(正确!)
void* producer(void *arg)
{
while(1)
{
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = rand()%100;
// 加锁(开始原子区)
pthread_mutex_lock(&mutex);
// 这两行被锁保护 → 变成逻辑原子
newNode->next = head;
head = newNode;
printf("+++product: %d\n", newNode->data);
// 解锁
pthread_mutex_unlock(&mutex);
sleep(rand()%3);
}
return NULL;
}
锁把两行代码变成 "逻辑原子操作"
- 同一时间只有一个线程能执行
- 执行时不会被其他线程打断
- 要么两行都执行完,要么都不执行
- 链表永远安全、不会崩溃、不会断链
四、核心总结
- 原子操作:不可被中断的一步操作
- 多步操作(链表增删、i++)不是原子操作
- 非原子操作在多线程下必须加锁
- 锁可以让多步指令变成 "逻辑上的原子操作"
- 不加锁 = 线程切换打断 = 数据混乱 / 程序崩溃
- 加锁 = 安全执行 = 逻辑原子