本期主题:
操作系统中的原子操作、自旋锁的底层实现
往期链接:
linux设备驱动中的并发
操作系统中原子操作、自旋锁、互斥锁
- 1. 问题描述
- 2. 原子操作
- 3. 自旋锁
- 4. 其他
1. 问题描述
在操作系统中,创建了多个线程,多个线程如果都访问同一个变量,没有做好互斥就会造成错误,就像下面的例子:
#include "thread.h"#define N 100000long sum = 0;void Tsum() {for (int i = 0; i < N; i++) {sum++;}
}int main() {create(Tsum);create(Tsum);join();printf("sum = %ld\n", sum);
}
测试结果:
执行10次,结果全都不对
根本原因:
没有做多线程之间的管理
2. 原子操作
我们可以使用 lock
汇编命令来保证原子操作
“lock addq $1, %0”:这条汇编指令执行一个加法操作,并使用 lock 前缀来确保操作是原子的。
- lock 前缀:在多处理器系统中,lock 前缀用于确保接下来的指令是原子的。它会锁定总线,防止其他处理器访问内存。
- addq $1, %0:将立即数 1 加到操作数 %0 上,q 表示操作数是 64 位的(quad word)
源码如下:
#include "thread.h"#define N 100000long sum = 0;void Tsum() {for (int i = 0; i < N; i++) {// sum++;// 这条命令可以保证原子加asm volatile("lock addq $1, %0": "+m"(sum));}
}int main() {create(Tsum);create(Tsum);join();printf("sum = %ld\n", sum);
}
测试结果:正确
其实c++本身提供了 atmoic的库,但是由于我们这里是讲解原理,所以就不写atmoic的相关方式了,感兴趣的朋友可以自己试一下。
3. 自旋锁
自旋锁(spinlock)是一种用于多线程编程的锁机制。它是一种忙等待锁,当一个线程尝试获取锁时,如果锁已经被其他线程占用,该线程不会进入睡眠状态,而是持续地在循环中检查锁是否可用,直到获取锁或达到超时时间。
1. 自旋锁的优点包括:
- 低开销:自旋锁不涉及线程上下文切换,适用于锁持有时间较短的场景。
- 简单实现:实现相对简单,适用于在硬件级别实现同步的情况。
2. 自旋锁的缺点包括:
- 资源浪费:自旋锁在等待锁释放期间会消耗CPU资源,因此不适用于锁持有时间较长的场景。
- 潜在死锁:如果没有妥善处理,可能会导致死锁。
我们可以使用xchg命令
来实现自旋锁,xchg命令介绍:
“lock xchg %0, %1”: lock前缀保证在多处理器系统中操作的原子性。xchg是交换指令,用于交换两个操作数的值。
#include "thread.h"#define N 100000int xchg(volatile int *addr, int newval) {int result;//用于交换addr地址对应的值和newval,并返回旧值asm volatile ("lock xchg %0, %1": "+m"(*addr), "=a"(result) : "1"(newval));return result;
}int locked = 0;
//locked被交换为1,返回的旧值只有是0的情况下才能退出循环
void lock() { while (xchg(&locked, 1)) ; }
//locker被交换为0
void unlock() { xchg(&locked, 0); }long sum = 0;void Tsum() {for (int i = 0; i < N; i++) {lock();sum++;unlock();}
}int main() {create(Tsum);create(Tsum);join();printf("sum = %ld\n", sum);
}
测试结果:
4. 其他
这是thread.h源码,创建进程的头文件:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdatomic.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>#define NTHREAD 64
enum { T_FREE = 0, T_LIVE, T_DEAD, };
struct thread {int id, status;pthread_t thread;void (*entry)(int);
};struct thread tpool[NTHREAD], *tptr = tpool;void *wrapper(void *arg) {struct thread *thread = (struct thread *)arg;thread->entry(thread->id);return NULL;
}void create(void *fn) {assert(tptr - tpool < NTHREAD);*tptr = (struct thread) {.id = tptr - tpool + 1,.status = T_LIVE,.entry = fn,};pthread_create(&(tptr->thread), NULL, wrapper, tptr);++tptr;
}void join() {for (int i = 0; i < NTHREAD; i++) {struct thread *t = &tpool[i];if (t->status == T_LIVE) {pthread_join(t->thread, NULL);t->status = T_DEAD;}}
}__attribute__((destructor)) void cleanup() {join();
}