进程间是否共享内存?
栈空间有多大?
来看一个例子,基于线程库的编程
谁主导了这一切?
多线程比单线程多了什么?
- 放弃原子性
- 放弃执行顺序
- 放弃多处理器间内存访问的即时可见性
山寨支付宝多线程支付
对共享内存的修改,导致两次读到不同的x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
Compile and link with -pthread.
The pthread_join() function waits for the thread specified by
thread to terminate. If that thread has already terminated, then
pthread_join() returns immediately. The thread specified by
thread must be joinable.
If retval is not NULL, then pthread_join() copies the exit status
of the target thread (i.e., the value that the target thread
supplied to pthread_exit(3)) into the location pointed to by
retval. If the target thread was canceled, then PTHREAD_CANCELED
is placed in the location pointed to by retval.
If multiple threads simultaneously try to join with the same
thread, the results are undefined. If the thread calling
pthread_join() is canceled, then the target thread will remain
joinable (i.e., it will not be detached).
|
pthread_join()函数可以阻塞当前线程,直到指定的线程完成执行为止。它的作用是等待一个线程结束并回收它的资源,同时将线程的返回值传递给调用线程。
不正常的sum
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#include "thread.h"
#define N 100000000
long sum = 0;
void Tsum() {
for (int i = 0; i < N; i++) {
//sum++;
asm volatile(
"incq %0" : "+m"(sum)
);
}
}
int main() {
create(Tsum);
create(Tsum);
join();
printf("sum = %ld\n", sum);
}
|
你可能得不到200000000这个答案,为什么?
哦,这该死的编译器
其他意想不到的问题:
1
2
3
4
5
6
7
8
9
10
11
12
|
int x = 0, y = 0;
void T1() {
x = 1; // Store(x)
printf("%d", y); // Load(y)
}
void T2() {
y = 1; // Store(y)
printf("%d", x); // Load(x)
}
|
理论上不会出现0 0
实际上最多出现的就是0 0
1
2
3
4
5
|
$ ./a.out | head -n 1000000 |sort |uniq -c
902562 0 0
92944 0 1
4461 1 0
33 1 1
|
当你排除了一切不可能,不是代码,编译器的问题,只有在执行指令的时候除了问题
store(x) 和load(x)没有数据依赖,被重新排列了
卧槽,这cpu,跟我玩阴的。
当 x≠=y 时,对 x,y 的内存读写可以交换顺序
它们甚至可以在同一个周期里完成 (只要 load/store unit 支持)
如果写 x 发生 cache miss,可以让读 y 先执行满足 “尽可能执行 μop” 的原则,最大化处理器性能.
x86的内存模型是宽松内存模型
是你能理解的正常的内存模型
ARM/RISC-V的内存模型就很奇葩
体系结构的不同导致跨指令集翻译变得困难,Rosstta && Latx如何实现?谁也不知道
多线程那么困难,为什么还要怎么做?
Proformance!
榨干性能
所以,当我们写并发程序时,请不要相信直觉、编译器、处理器,请加锁。
然而,人类本质上是物理世界 (宏观时间) 中的 “sequential creature”,因此我们在编程时,也 “只能” 习惯单线程的顺序/选择/循环结构,真实多处理器上的并发编程是非常具有挑战性的 “底层技术”,例如 Ad hoc synchronization 引发了很多系统软件中的 bugs。因此,我们需要并发控制技术 (之后的课程涉及),使得程序能在不共享内存的时候并行执行,并且在需要共享内存时的行为能够 “容易理解”。