线程
为什么使用它?
- 可以并发执行
- 线程间共享相同的地址空间、资源。(进程不可以)
定义:
- 进程中的一条执行路径。
TCB:每个线程有自己的PC SP 寄存器。
使用场景:
线程:比如天气预报复杂计算适用于线程。
进程:比如浏览器,开启问题网页容易导致整个浏览器崩溃,所以每个网页是一个独立进程,互相不受影响。 - 数量关系
- 1对1 DOS系统单进程单线程
- 早期Unix多进程,每个进程1个线程
- Windos NT 多进程,每个进程对应多个线程
对于多线程,每个线程有自己独立的PC,寄存器,栈。共享进程的代码段、数据段、文件、网络等。
进程线程区别
- 资源组合方面:进程把一组资源组合到一起,包括地址空间(代码段、数据段)、打开文件、网络等。
- 运行方面:线程是在这个资源平台上的一条执行流程。
- 进程是分配资源内存,线程是执行单元。
- 线程创建和终止时间短(不用考虑资源的创建和销毁)
- 线程切换快于进程切换(线程共享同一个页表,进程切换要调用不同的页表,切换页表地址空间不同、cache不同、TLB不同导致开销大)
线程的实现
- 三种实现方式
- 用户线程:用户空间实现操作系统看不见、由用户线程库进行管理
- 内核线程:内核中实现(操作系统管理,可看见)
- 轻量级进程:在内核中实现,支持用户线程。
- 用户线程与内核线程对应关系
- 一对一
- 多对一
- 多对多
如图,用户线程是由TCB管理,操作系统看不到TCB。
- 用户线程
- 用户空间实现的线程机制,不依赖于操作系统内核,由一组用户级的线程库函数来完成线程的管理,包括创建、终止、同步、调度。
- 用户线程维护由进程完成(通过线程库函数),不需要操作系统内核了解用户线程的存在
- 每个进程都有自己的私有TCB(线程控制块)列表,用来跟踪记录各个线程状态(PC、栈指针、寄存器),TCB由线程库函数维护。
- 用户线程切换也由线程库函数完成,无需用户态/内核态转换。速度快。
- 允许每个进程拥有自定义的线程调度算法。
针对第二点:由于操作系统管理中断,可以更换执行进程。操作系统无法控制用户线程,用户线程无法更换执行线程,所以需要主动交出CPU才能执行其他用户线程。
- 用户空间实现的线程机制,不依赖于操作系统内核,由一组用户级的线程库函数来完成线程的管理,包括创建、终止、同步、调度。
用户线程就是为了并发,可以创建的数量很多,而且不经过操作系统。
- 内核线程
TCB放在内核里
针对第二点:线程的创建、终止、切换都要经过用户态到内核态的转化,消耗大。 - 轻量级进程(LightWeight Process)
进程上下文切换
将每个进程的信息存储到PCB中,切换到哪个进程,去PCB读取当前进程信息,CPU在去执行。
系统将PCB放置在一个合适的队列里。(阻塞队列,就绪队列,僵尸队列)
进程创建
- 创建新进程系统调用:fork/exec
- fork()把一个进程复制成两个进程
- parent(Old id)child(new id)
- exec()用新程序重写当前进程
- pid没有改变
- fork()把一个进程复制成两个进程
// A code block
int pid = fork(); // create child process
if(pid == 0){ //child process continue
//do anything ummap memory ,close net connections...
exec("program",arg1,arg2,arg3...);
}
fork以后父子进程返回值不同,pid=0是子进程。
- fork创建个继承的子进程
- 复制父进程的所有变量和内存
- 复制父进程的所有CPU寄存器(一个除外PID)
- fork返回值
- 子进程返回0
- 父进程返回子进程id
fork以后复制父进程所有信息,此时,父进程和子进程除了pid,其余一样,执行exec以后,子进程的代码数据堆栈就按照exec的参数发生了变化。
- fork开销
- 对子进程分配内存
- 复制父进程的内存和CPU寄存器信息
- 复制信息,开销大
- VFork()[virtual Fork]
- 创建进程时不再创建一个同样的内存镜像
- 轻量级fork
- 子进程应该几乎立即调用exec
- Copy on Write技术(需要写的时候再去复制)
进程控制-加载和执行
执行exec后,代码执行更换。
执行exec后,代码段发生变化,PCB信息也发生了变化。
- exec调用允许一个进程“加载”一个不同的程序,所以新建的进程可以执行父进程的程序,也可以执行不同的程序(不调exec)。
- 执行exec的时候,进程本身的堆、栈、代码段都会被覆盖。
进程控制-等待和终止
- wait()系统调用是父进程用来等待子进程结束的,等子进程结束后,在操作系统中回收子进程PCB资源。配合子进程的exit完成对子进程所有资源的回收。
- 当子进程调用exit,操作系统解锁父进程,通过exit返回的结果找到子进程,如果没有子进程直接返回。
- 僵尸态:子进程执行完exit,父进程没有执行完wait,没有回收完资源的状态。就是进程调用了exit,但是pcb还没被回收的时候。
- 如果父进程死亡,子进程的exit后资源的回收工作,交给root进程,就是所有进程的根进程。
如图,在子进程调用完exit以后,parent的wait操作还没执行完的时候,处于僵尸态。fork-》new。调用exec的时候,进程可能出于不同状态,运行状态,exec会去执行新代码,如果新代码需要从磁盘都到内存中,速度慢,这时候进程还可能出于阻塞状态。