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

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


往期链接:
linux设备驱动中的并发


操作系统中原子操作、自旋锁、互斥锁


1. 问题描述

在操作系统中,创建了多个线程,多个线程如果都访问同一个变量,没有做好互斥就会造成错误,就像下面的例子:

#include "thread.h"

#define N 100000

long 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 100000

long 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 100000

int 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();
}

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值