您的位置:首页 > 房产 > 建筑 > 操作系统中的多线程问题——原子操作、自旋锁的底层实现

操作系统中的多线程问题——原子操作、自旋锁的底层实现

2024/10/7 10:14:00 来源:https://blog.csdn.net/weixin_37620587/article/details/140419001  浏览:    关键词:操作系统中的多线程问题——原子操作、自旋锁的底层实现

本期主题:
操作系统中的原子操作、自旋锁的底层实现


往期链接:
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();
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com