这篇文章先会主要讨论下线程的定义,结构,想较于进程的优点和缺点. 然后会说说多核编程以及他的优点. 接着会说下用户线程和内核线程, 最后会引出线程模型.
线程学习
1. 定义(Definition):
线程的概念说法有很多种,我觉得比较好理解的就是:线程是操作系统中可被执行的最小单元, 也可被理解成 CPU资源分配的基本单元(basic unit of CPU ultilization)。 在目前的操作系统中,他通常存在于各个进程当中。 而每个process可同时拥有多个线程。
2. 结构(structure):
![](https://lh4.googleusercontent.com/M8HcC6l1BHB1euCWzxClre-afC5Ca7gT04GL4L4Vdm7eZfgAwlJ4xTkvJ6ij4I4jQIzhvVzBS06FrAoNMrYEYdSLu-0w6aiRtpzTHXSLLt5isGNDLy3OLG45wZYsotqw5S1WTRwk)
(Operating System 9th ed, P164)
从上图可以看出, 当一个process创建多个线程的时候, 所有的线程都会存在于这个process里面。 而这个process的code, data, files会被shared by default (注意这点跟process不同)。 但是其stack和register并不会share。
擼个小代码来说说就是:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
static int NUM = 5;
void *print_msg( void* );
main()
{
pthread_t t1, t2; /* two threads */
pthread_create(&t1, NULL, print_msg, (void *)"hello");//Thread Creation and Implementation
pthread_create(&t2, NULL, print_msg, (void *)"world\n");
pthread_join(t1, NULL); //Wait t1
pthread_join(t2, NULL); //wait t2
exit(1);
}
void *print_msg(void *m)
{
int i;
for( i=0 ; i < NUM ; i ++ ){
printf("%s", m);
sleep(1);
}
return NULL;
}
编译并执行: gcc -o run xx.c -pthread && ./run 。我们会发现两个线程都会执行相同次数(NUM)的输出, 并且应带入的局部指针(存在于在stack上)的不同而输出不同的结果。
3. TCB (Thread control block)
与进程的PCB类似,每个线程也有他的TCB。 其中包含:
1). Thread ID
2). 栈指针(Stack Pointer)
3). PC(Program Counter)
4). 线程状态(State of Thread)
5). 指向PCB的指针(Pointer to PCB)
4. Benefit
1). Responsiveness(响应性): 向比于进程,一个线程可以在其他线程被阻塞(blocked)的时候更快的对用户操作作出响应。
2). Economy(经济性): 相比于进程,一个线程的创建管理和消除都更便宜。 并且在thread之间的context switch向较于进程会更加迅速
3). Scalability(扩展性): 相比于多进程,多线程可以被更好的用在并行处理方面。(比如说 multi-core processor, distributed system, Hyper-Threading<详情见附注1>)
4). Resource sharing(资源共享性): 在我眼里,这个可以算优点同时也是缺点。 因为相比于进程之间的user定义的通信,每个线程在创建后都会默认于其他线程共享data, files, code。 因此,这可以允许多个任务都会在单个物理地址上被同时处理。但是,在某种意义上来讲也是缺点。 假如说我有t1 t2
t3三个线程共享一个全局变量并对其进行运算, 假如其中任意一个的运算出现错误,那么得嘞,仨全错~
附注1:
1. 超线程(Hyper-Threading): Intel 2002年提出来的一个技术。 在CPU处理一个instruction的时候,他并不能完全的使用掉CPU的全部资源,事实上他的大多东西依然处于一个idle状态。打个比方,如果我这条instructiobn处理的是一个整数类型的加减,那么当前CPU的浮点单元就处于闲置状态。 超线程的技术将一个物理内核模拟成两个逻辑内核, 让单个processor都能进行线程级别的并行计算。所以跟着之前的例子,在超线程的状态下,就可以一个逻辑内核处理一个整数类型的运算另外一个处理一个浮点类型的运算了。因此可以大大减少CPU idle时间,提高运行速度。
5. Multicore programming(多核编程)
*其实讲道理,多核编程对多个进程肯定也是100%有效的, 并不能理解为啥要在线程这章里面提.
5.1 定义:
其实说的就是现在的计算机大多数都有多个core(核心),也就是多个CPU. 这样程序们都可以被并行性质的处理, 效率能够提升不少. 但是我觉得缺点就是程序擼起来, 难!麻烦!恶心! 程序员们估计又要多掉几根头发了吧~
5.2 难题:
*其实都是针对程序员软件编程方面的,硬件方面现在已经很牛逼了! 记得那会儿学功能性编程语言Scheme的时候, 傻叼教授一直给那bb这点破事儿(手动摇头) ~~
1). Dividing activities - How can we make better use of parallel processing?
2). Balance - How should we balance the parallel tasks on the available cores to get maximum efficiency?
3). Data splitting - How can datasets be split for processing in parallel and then rejoined.
4). Data dependency - Some process must be performed in a certain order so synchronization of processes/threads will be necessary.
5). How to test and debug such system?
5.3 图解Concurrent 和 Parallelism 的运行区别:
可以看出,多核所处理Multi-task的时间比单个CPU效率要高.顺着逻辑来,现在的计算机如果都是多核的话,假设我的一个任务如果把他分成多个线程来做的话, 所花的时间应该比他按单线程来作少很多.
6. User Thread VS Kernel Thread
6.1 概念
大多数User Thread都可以被用户操控,并且在context switch的时候非常简单. 在第一篇文章中提到过, 用户等级的进程是永远进入不了内核的. 进程都进不去,他里面的那堆thread肯定也进不去. 这个时候就涉及到 Kernel Thread的知识点了. 每个Kernel Thread都会对应一个或者多个User Thread, 当User Thread遇到大于其权限,完成不了的任务的时候, pc(program counter)就会指向所对应的Kernel Thread, 让后者帮他完成(In the specific implementation, user thread must map to kernel thread). 因此,在 Kernel的眼里只有Kernel Thread, User Thread他是永远看不见的! 由此牵引出了很重要的一点就是, 所有的User Thread 已 non-blocking system call为基本条件. 因为如果其中的一个thread被阻塞了,那么整个的process都会被阻塞.这个缺点在Many-to-one Model(见6.2)里面尤其明显.
User Thread VS Kernel Thread |
在运行速度,管理以及响应性方面, User Thread 都会优于kernel Thread |
User Thread被用户操控 | Kernel Thread 被OS系统操控 |
OS永远看不见用户等级的线程 | OS可以看见Kernel Thread |
User Thread的实现很简单 | Kernel Thread的实现复杂一些 |
花较少的时间进行Context Switch | 花较多的时间尽心Context Switch |
Context Switch不需要hardware support | Context Switch需要hardware support |
如果一个User Thread被阻塞住,整个进程都将会被阻塞住. | 如果一个Kernel Thread被阻塞了,其他的线程可以继续进行操作. |
Example: POSIX Thread | Example: Windows Solaris |
6.2 Threading Model
1. Many-to-one Model (多对一模型)
(Operating System 9th ed, P169)
特性:
1. 多对一模型被早期的计算机较多使用, 使用的技术就是多个用户线程对应一个内核线程.
2. 用户级别的线程都被thread libarary所管理, 非常的有效率.
3. 如果有一个线程呼叫了blocking system call, 其他可以跑的线程也会被卡住.因为一个CPU只能跑一个内核线程,这个模型不支持将一个process的其他线程分配给其他CPU.
4. Example: Solaris Green Thread & GNU Portable Thread
2.One-to-one Model(一对一模型)
(Operating System 9th ed, P170)
特性:
1. 这个模型为每个user thread都创建一个kernel thread
2. 这个模型完美的解决了Many-to-one Model的问题
3. 但是新的问题出现啦! 这个模型实在是太过浪费了, 首先并不是每个user thread都需要进行高权限操作的,我可能只想print一个"hello world!". 假设如果我创建了1000个user thread, 那系统就要给我准备1000个kernel thread, 整个系统的运行速度都会慢下来.所以大多数使用这个模型的操作系统都会限制可以制造的thread数量来避免这个惨剧~
4. Example: Linux, Windows 95 ~ XP.
3.Many-to-Many Model(多对多模型)
(Operating System 9th ed, P170)
特性:
1. 这个模型采用了前面俩的优点(kernel Thread的数量 <= User Thread的数量)
2. User可以创建任意数量的User Thread
3. 一个线程的阻塞并不会影响整个进程, 并且剩余的线程可以分配给其他processor
4. 一个进程可能会被分配给 小于等于kernel 数量的CPU, 所以会被更有效率的运行
5. Example: Windows NT/2000 with ThreadFiber package
4.Two-level Model(二级模型)
(Operating System 9th ed, P171)
特性:
1. 性质大致于Many-to-many Model 相同, 但是可以允许user设置某个User Thread与Kernel Thread直接绑定起来.当下也很流行
2. Example: HP-UX, IRIX 好吧,这俩并没有听说过, 哈哈~
嘛, 到这了~