【并发1】多处理器编程:从入门到放弃

本文是操作系统课程的笔记,探讨并发与并行的概念,解释了多处理器编程中的线程、并发性和并行性。介绍了POSIX Threads、线程共享与独立的数据以及并发控制面临的挑战,如原子性、顺序性和可见性问题。
摘要由CSDN通过智能技术生成

这是 bilibili-[完结] 2020 南京大学 “操作系统:设计与实现” (蒋炎岩) 的课程笔记

本讲概要:
什么是并发、为什么需要并发、并发编程初识
放弃程序的原子性、顺序性、可见性

并发与并行

假设系统只有一个CPU

操作系统可以同时加载多个程序(进程)

  • 每个进程都有独立的地址空间里,不会互相干扰
  • 即便有root权限的进程,也不会直接访问操作系统内核的内存
  • 每隔一段时间就切换到另一个进程进行

多任务操作系统中的并发:

并发性的来源:进程会调用操作系统的API

  • write(fd, bug, 1 TiB)

  • write 的实现是操作系统的一部分

    • x86-64 应用程序执行syscall后就进入操作系统执行(应用程序不可见)
    • 类似中断处理程序
    • 运行在处理器的高特权级:能访问硬件设备(否则就不能写数据了)
    • 不能一直霸占着处理器运行(否则系统就卡死了)
  • 因此必须允许write到一半的时候,让另一个进程执行

    • 另一个进程调用read(fd, buf, 512 MiB)读取同一个文件
    • 操作系统API需要考虑并发

并发(Concurrency):多个执行流可以不按照一个特定的顺序执行

并行(Parallelism):允许多个执行流同时执行(多处理器)

处理器数量 共享内存 典型的并发系统 并发并行
单处理器 共享内存 OS内核/多线程程序 并发不并行
多处理器 共享内存 OS内核/多线程程序/GPU Kernel 并发并行
多处理器 不共享内存 分布式系统(消息通信) 并发并行

多处理器编程:入门

线程

线程:多个执行流并发/并发执行,并且他们共享内存

  • 两个执行流共享代码和所有全局变量(数据、堆区)
  • 线程之间的执行顺序是不确定的(non-deterministic)的
int x = 0, y = 0;

void thread_1(){
   
    x  = 1; // [1]
    printf("y = %d\n",y); // [2]
}

void thread_2(){
   
    y = 1; // [3]
    printf("x = %d\n",x); // [4]
}

1 - 2 - 3 - 4 (y=0,x=1)

1 - 3- 2 - 4 (y=1, x=1)

线程:什么该共享、什么不共享?
extern int x;
int foo(){
   
    int volatile t = x;
    t += 1;
    x = t;
}

考虑如果有两个执行流同时调用foo,哪些资源是共享的?

  • foo的代码(1140_115f)
    • 这个函数可以被所有线程调用,所以是共享的
  • 寄存器:rip\rsp\rax
  • 变量:x:0x2eb5
    • 线程之间会共享数据,所以全局变量是共享的

除了代码和全局数据之外,每一个线程的堆栈和寄存器都是他们独享的

POSIX Threads
  • 使用 pthread_create 创建并运行线程
    • 得到若干个共享了当前地址空间的线程
  • 使用pthread_join 等待某个线程结束

可以使用man 7 pthreads查看pthreads的帮助文档

帮助文件man:

man 1:用户命令(可执行命令和shell程序)

man 2:系统调用(从用户空间调用的内核例程)

man 3:库函数(有程序库提供)

man 4:特殊文件(如设备文件)

man 5:文件格式(用于许多配置文件和结构)

man 6:游戏(过去的有趣程序章节)

man 7:惯例、标准和其他(协议、文件系统)

man 8:系统管理和特权命令(维护任务)

man 9:Linux 内核API(内核调用)

无论系统是单处理器还是多处理器,都得到了若干共享了当前进程地址空间的线程

  • 共享代码:所有线程的代码都来自于当前进程的代码
  • 共享数据:全局数据/堆区可以自由引用
  • 独立堆栈:每个线程有独立的堆栈
threads.h: Simplified Thread APIs

create(fn)

  • 创建并运行一个线程,该线程立即开始执行函数 fn
  • 函数原型 :void fn(int tid){}
  • tid从1开始编号

join(fn)

  • 当代所有线程执行结束
  • 执行函数 fn
  • 只能 join 一次
threads.h实现

数据结构:

struct thread {
   
  int id; // 线程号
  pthread_t thread; // pthread 线程api中的线程号
  void (*entry)(int); // 入口地址
  struct thread *next; // 链表
};

struct thread *threads; // 链表头
void (*join_fn)(); // 回调函数

线程创建实现:

static inline void *entry_all(void *arg) {
   
  struct thread *thread = (struct thread *)arg;
  thread->entry(thread->id);
  return NULL;
}

static inline void create(void *fn) {
   
  struct thread *cur = (struct thread *)malloc(sizeof(struct thread)); // 分配给线程内存
  assert(cur); //假设内存分配成功
  cur->id    = threads ? threads->id + 1 : 1; // 分配线程号
  cur->next  = threads;
  cur->entry = (void (*)(int))fn;
  threads    = cur;
  pthread_create(&cur->thread, NULL, entry_all, cur); // 调用posix的api
}

线程join实现

static inline void join(void (*fn)()) {
   
  join_fn = fn;
}

__attribute__((destructor)) static void join_all(
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值