复习
本次课回答的问题
本次课主要内容
thread.h
Concurrent: existing, happening, or done at the same time.
In computer science, concurrency refers to the ability of different parts or units of a program, algorithm, or problem to be executed out-of-order or in partial order, without affecting the final outcome. (Wikipedia)
为什么在这门课 (先) 讲并发?
malloc
/free
)共享内存的多个执行流
用状态机的视角就很容易理解了!
thread.h
简化的线程 API我们为大家封装了超级好用的线程 API (thread.h
)
create(fn)
fn
的线程,并立即开始执行void fn(int tid) { ... }
tid
从 1 开始编号fn(tid)
join()
fn
返回main
返回时会自动等待所有线程结束-lpthread
Hello, Multi-threaded World!
#include "thread.h"
void Ta() { while (1) { printf("a"); } }
void Tb() { while (1) { printf("b"); } }
int main() {
create(Ta);
create(Tb);
}
利用 thread.h
就可以写出利用多处理器的程序!
会编程,你就拥有全世界!
如何证明线程确实共享内存?
如何证明线程具有独立堆栈 (以及确定它们的范围)?
sort
!)更多的习题
thread.h
背后:POSIX Threads想进一步配置线程?
POSIX 为我们提供了线程库 (pthreads)
man 7 pthreads
然而,可怕的事情正在悄悄逼近……
x++
,结果会是什么呢?unsigned int balance = 100;
int Alipay_withdraw(int amt) {
if (balance >= amt) {
balance -= amt;
return SUCCESS;
} else {
return FAIL;
}
}
两个线程并发支付 ¥100 会发生什么?alipay.c
分两个线程,计算 $1+1+1+\ldots+1$ (共计 $2n$ 个 $1$)
#define N 100000000
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);
}
sum.c 运行结果
N
还要小), ...“程序 (甚至是一条指令) 独占处理器执行” 的基本假设在现代多处理器系统上不再成立。
原子性:一段代码执行 (例如 pay()
) 独占整个计算机系统
(历史) 1960s,大家争先在共享内存上实现原子性 (互斥)
printf
还能在多线程程序里调用吗?
void thread1() { while (1) { printf("a"); } }
void thread2() { while (1) { printf("b"); } }
我们都知道 printf 是有缓冲区的 (为什么?)
buf[pos++] = ch
(pos
共享) 不就 💥 了吗?RTFM!
互斥和原子性是本学期的重要主题
lock(&lk)
unlock(&lk)
99% 的并发问题都可以用一个队列解决
分两个线程,计算 $1+1+1+\ldots+1$ (共计 $2n$ 个 $1$)
#define N 100000000
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);
}
我们好像忘记给 sum.c 添加编译优化了?
-O1
: 100000000 😱😱-O2
: 200000000 😱😱😱编译器对内存访问 “eventually consistent” 的处理导致共享内存作为线程同步工具的失效。
刚才的例子
-O1
: R[eax] = sum; R[eax] += N; sum = R[eax]
-O2
: sum += N;
另一个例子
while (!done);
// would be optimized to
if (!done) while (1);
在代码中插入 “优化不能穿越” 的 barrier
asm volatile ("" ::: "memory");
volatile
变量extern int volatile done;
while (!done) ;
int x = 0, y = 0;
void T1() {
x = 1;
asm volatile("" : : "memory"); // compiler barrier
printf("y = %d\n", y);
}
void T2() {
y = 1;
asm volatile("" : : "memory"); // compiler barrier
printf("x = %d\n", x);
}
问题:我们最终能看到哪些结果?
head -n 1000000 | sort | uniq -c
单个处理器把汇编代码 (用电路) “编译” 成更小的 $\mu$ops
在任何时刻,处理器都维护一个 $\mu$op 的 “池子”
满足单处理器 eventual memory consistency 的执行,在多处理器上可能无法序列化!
当 $x \ne y$ 时,对 $x$, $y$ 的内存读写可以交换顺序
# <-----------+
movl $1, (x) # |
movl (y), %eax # --+
宽松内存模型的目的是使单处理器的执行更高效。
x86 已经是市面上能买到的 “最强” 的内存模型了 😂
软件做不到,硬件来帮忙
__sync_synchronize()
(RTFM)lock
prefix, lr/sc, ...)stdatomic.h
本次课回答的问题
Take-away message