本讲概要:
什么是并发、为什么需要并发、并发编程初识
放弃程序的原子性、顺序性、可见性
目录
并发与并行
假设系统只有一个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(