线程共享内存与独立栈
2022-07-06 09:00:00

写在前面

本文是针对南大蒋炎岩老师 OS 课程 PPT 的个人测试,课程相关视频在此

此处指出了线程贡献和独占的资源,可以看出,线程独占一个栈;但是其中没有指出的是,对于存储在数据区的全局变量、静态变量和常量,线程也是可以共享的。

线程是否共享内存

以下代码中,设置了全局变量x,并启动 10 个线程,执行同样的任务函数Thello;在任务函数Thello中,睡眠以确保线程不会冲突,然后依次读取x++的值。作为线程共享的全局变量,每次赋值操作也会影响到其他线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "thread.h"

int x = 0;

void Thello(int id) {
usleep(id * 100000);
printf("Hello from thread #%c\n", "123456789ABCDEF"[x++]);
}

int main() {
for (int i = 0; i < 10; i++) {
create(Thello);
}
}
1
2
3
4
5
6
7
8
9
10
Hello from thread #1
Hello from thread #2
Hello from thread #3
Hello from thread #4
Hello from thread #5
Hello from thread #6
Hello from thread #7
Hello from thread #8
Hello from thread #9
Hello from thread #A

但如果将x设置为Thello中的局部变量,则输出全部为 1。

线程是否拥有独立栈

启动四个线程,任务函数为Tprobe,在任务函数中无限递归函数stackoverflowTporbe参数tid应该是在线程栈的前部位置,通过base指针变量记录参数tid的地址,找到线程栈的开头;在stackoverflow的一次次递归中,向下探索栈的极限,每增大 1KB 就输出一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "thread.h"

__thread char *base, *cur; // thread-local variables
__thread int id;

// objdump to see how thread-local variables are implemented
__attribute__((noinline)) void set_cur(void *ptr) { cur = ptr; }
__attribute__((noinline)) char *get_cur() { return cur; }

void stackoverflow(int n) {
set_cur(&n);
if (n % 1024 == 0) {
int sz = base - get_cur();
printf("Stack size of T%d >= %d KB\n", id, sz / 1024);
}
stackoverflow(n + 1);
}

void Tprobe(int tid) {
id = tid;
base = (void *)&tid;
stackoverflow(0);
}

int main() {
setbuf(stdout, NULL);
for (int i = 0; i < 4; i++) {
create(Tprobe);
}
}

输出内容过多,通过 grep 和 sort 指令后得到其各自的输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$ ./a.out > output
$ cat output | grep "T1" | sort -nk 6
Stack size of T1 >= 0 KB
Stack size of T1 >= 64 KB
Stack size of T1 >= 128 KB
...
Stack size of T1 >= 8000 KB
Stack size of T1 >= 8064 KB
Stack size of T1 >= 8128 KB

$ cat output | grep "T2" | sort -nk 6
Stack size of T2 >= 0 KB
Stack size of T2 >= 64 KB
Stack size of T2 >= 128 KB
...
Stack size of T2 >= 7552 KB
Stack size of T2 >= 7616 KB
Stack size of T2 >= 7680 KB

$ cat output | grep "T3" | sort -nk 6
Stack size of T3 >= 0 KB
Stack size of T3 >= 64 KB
Stack size of T3 >= 128 KB
...
Stack size of T3 >= 6144 KB
Stack size of T3 >= 6208 KB
Stack size of T3 >= 6272 KB

$ cat output | grep "T4" | sort -nk 6
Stack size of T4 >= 0 KB
Stack size of T4 >= 64 KB
Stack size of T4 >= 128 KB
...
Stack size of T4 >= 6976 KB
Stack size of T4 >= 7040 KB
Stack size of T4 >= 7104 KB

对于线程T1,最终输出为 8128KB,近似 8192KB,也就是 8MB。查询线程大小限制也是得知为 8MB。

1
2
$ ulimit -s
8192

而当线程T1爆栈后,进程退出,其他线程没有增长到最大就退出了。