本文旨在剖析线程的5个W和1个H。
0x00 什么是线程(what and why?)
线程常常与进程一起谈及,可通过以下解释它们的关系:
进程相当于一个项目,而线程则是为了完成项目需求,而建立的一个个开发任务。
因此,当开发任务间无先后次序时,拆分出支线来方便并发,可以使效率up up(科学的分工,符合宇宙法則)。
0x01 如何创建线程(how?)
创建前,先了解调用线程的必经步骤。
根据流程图,构建与之对应的测试代码。
1 |
|
对代码进行编译,多次运行观察线程的随机创建(线程创建全靠资源动态分配)。
1 | [root@learn ~]# gcc download.c -lpthread -o download.out |
0x02 线程的数据(when and where?)
了解完并发多线程的运作模式和优势,接着是线程对三种不同位置类别数据的处理方式。
线程栈上的本地数据
默认被赋予了每个线程8MB的线程栈空间,用于存储其中的局部变量。
1 | [root@learn ~]# ulimit -a |
而内存中线程栈间有用小块区域来隔离各自空间,当被踏入则会引发段错误(Segmentation fault)。
可以通过函数pthread_attr_t来修改单个线程栈大小,或者通过ulimit -s来进行全局修改。
1 | int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize); |
进程中共享的全局数据
线程在读取进程中共享的全局数据时,引入互斥锁Mutex(Mutual Exclusion)来避免数据不一,需要等待互斥锁[(pthread_mutex_lock()] 或尝试互斥锁[pthread_mutex_trylock()] 來保证独占。 前者需要一直等待,后者则可以根据没抢到的提示佛系抢占。
无条件变量的等待互斥锁
有无锁的情况对比,可帮助明白锁的重要性。
无互斥锁,受多线程影响的全局变量(money_of_tom + money_of_jerry):
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120[root@learn ~]# cat mutex.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_OF_TASKS 5
int money_of_tom = 100;
int money_of_jerry = 100;
//暂不启用mutex
//pthread_mutex_t g_money_lock;
//创建子线程用于执行金额转账
void *transfer(void *notused)
{
pthread_t tid = pthread_self();
printf("Thread %u is transfering money!\n", (unsigned int)tid);
//暂不启用mutex
//pthread_mutex_lock(&g_money_lock);
sleep(rand()%10);
money_of_tom+=10;
sleep(rand()%10);
money_of_jerry-=10;
//暂不启用mutex
//pthread_mutex_unlock(&g_money_lock);
printf("Thread %u finish transfering money!\n", (unsigned int)tid);
pthread_exit((void *)0);
}
//主线程负责输出总额,子线程负责交易细节
int main(int argc, char *argv[])
{
pthread_t threads[NUM_OF_TASKS];
int rc;
int t;
//暂不启用mutex
//pthread_mutex_init(&g_money_lock, NULL);
for(t=0;t<NUM_OF_TASKS;t++){
//创建子线程开启转账
rc = pthread_create(&threads[t], NULL, transfer, NULL);
if (rc){
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
for(t=0;t<18;t++){
//暂不启用mutex
//pthread_mutex_lock(&g_money_lock);
printf("money_of_tom + money_of_jerry = %d\n", money_of_tom + money_of_jerry);
//随机输出时间
sleep(rand()%3);
//暂不启用mutex
//pthread_mutex_unlock(&g_money_lock);
}
//暂不启用mutex
//pthread_mutex_destroy(&g_money_lock);
pthread_exit(NULL);
}
[root@learn ~]# gcc mutex.c -lpthread -o before.out
[root@learn ~]# ./before.out
Thread 4143912704 is transfering money!
Thread 4135520000 is transfering money!
Thread 4152305408 is transfering money!
Thread 4127127296 is transfering money!
money_of_tom + money_of_jerry = 200
Thread 4118734592 is transfering money!
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 210
money_of_tom + money_of_jerry = 210
money_of_tom + money_of_jerry = 210
Thread 4118734592 finish transfering money!
Thread 4143912704 finish transfering money!
money_of_tom + money_of_jerry = 220
money_of_tom + money_of_jerry = 230
money_of_tom + money_of_jerry = 230
money_of_tom + money_of_jerry = 230
money_of_tom + money_of_jerry = 230
money_of_tom + money_of_jerry = 230
Thread 4127127296 finish transfering money!
money_of_tom + money_of_jerry = 220
Thread 4152305408 finish transfering money!
money_of_tom + money_of_jerry = 210
money_of_tom + money_of_jerry = 210
money_of_tom + money_of_jerry = 210
Thread 4135520000 finish transfering money!
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200
[root@learn ~]# ./before.out
money_of_tom + money_of_jerry = 200
Thread 4152305408 is transfering money!
Thread 4135520000 is transfering money!
Thread 4118734592 is transfering money!
Thread 4143912704 is transfering money!
Thread 4127127296 is transfering money!
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 210
money_of_tom + money_of_jerry = 210
Thread 4143912704 finish transfering money!
money_of_tom + money_of_jerry = 230
money_of_tom + money_of_jerry = 230
money_of_tom + money_of_jerry = 230
money_of_tom + money_of_jerry = 240
Thread 4127127296 finish transfering money!
money_of_tom + money_of_jerry = 230
money_of_tom + money_of_jerry = 230
money_of_tom + money_of_jerry = 230
money_of_tom + money_of_jerry = 230
Thread 4152305408 finish transfering money!
Thread 4135520000 finish transfering money!
money_of_tom + money_of_jerry = 210
Thread 4118734592 finish transfering money!
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200有互斥锁,受多线程影响的全局变量(money_of_tom + money_of_jerry):
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78[root@learn ~]# cat mutex.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_OF_TASKS 5
int money_of_tom = 100;
int money_of_jerry = 100;
//第一次运行去掉下面这行
pthread_mutex_t g_money_lock;
void *transfer(void *notused)
{
pthread_t tid = pthread_self();
printf("Thread %u is transfering money!\n", (unsigned int)tid);
//开始转账前,等待互斥锁独占共享变量
pthread_mutex_lock(&g_money_lock);
sleep(rand()%10);
money_of_tom+=10;
sleep(rand()%10);
money_of_jerry-=10;
//完成转账后,释放互斥锁
pthread_mutex_unlock(&g_money_lock);
printf("Thread %u finish transfering money!\n", (unsigned int)tid);
pthread_exit((void *)0);
}
int main(int argc, char *argv[])
{
pthread_t threads[NUM_OF_TASKS];
int rc;
int t;
//开始创建线程前,初始化一个互斥锁 &g_money_lock
pthread_mutex_init(&g_money_lock, NULL);
for(t=0;t<NUM_OF_TASKS;t++){
rc = pthread_create(&threads[t], NULL, transfer, NULL);
if (rc){
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
for(t=0;t<100;t++){
//开始打印共享变量前,等待互斥锁独占共享变量
pthread_mutex_lock(&g_money_lock);
printf("money_of_tom + money_of_jerry = %d\n", money_of_tom + money_of_jerry);
sleep(rand()%10);
//完成输出后解锁
pthread_mutex_unlock(&g_money_lock);
}
//全部完成后,销毁锁
pthread_mutex_destroy(&g_money_lock);
pthread_exit(NULL);
}
[root@learn ~]# gcc mutex.c -lpthread -o after.out
[root@learn ~]# ./after.out
money_of_tom + money_of_jerry = 200
Thread 4152305408 is transfering money!
Thread 4143912704 is transfering money!
Thread 4118734592 is transfering money!
Thread 4135520000 is transfering money!
Thread 4127127296 is transfering money!
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200
...
money_of_tom + money_of_jerry = 200
Thread 4135520000 finish transfering money!
Thread 4152305408 finish transfering money!
Thread 4143912704 finish transfering money!
Thread 4118734592 finish transfering money!
money_of_tom + money_of_jerry = 200
money_of_tom + money_of_jerry = 200
...
money_of_tom + money_of_jerry = 200
Thread 4127127296 finish transfering money!有条件变量的等待互斥锁
根据流程图,构建与之对应的测试代码。
1 | [root@learn ~]# cat varmutex.c |
线程的私有数据
与本地数据的区别在于,前者只是局部变量,私有数据是于线程创建的全局变量,亦非进程的全局变量。
創建key对应的value
1 | int pthread_setspecific(pthread_key_t key, const void *value) |
获得key对应的value
1 | void *pthread_getspecific(pthread_key_t key) |
0x03 结语
主要阐述了三部分,what and why,how,when and where 。
当中how通过代码样例,示范了创建线程的步骤,创建并运行了一个多线程样例随机运行的情况。
在when and where中,通过比较,得出互斥锁对于保护线程操作数据的必要性。
之后在三种不同的线程数据中,重点解剖了有无变量控制下的互斥锁工作过程。
以下是总结贴图。
个人学习笔记,欢迎斧正。