引入线程
如下图,映射表就是资源,因为映射表代表的是内存
- 进程切换的时候只切换指令不切换资源,这就是线程的切换,也就是说在切换进程时,只切换线程,不切换资源,
- 这样既有多个指令的切换,又切起来快,也就是线程的优点:既保留了并发的优点,又避免了进程切换的代价
- 举例:
- 如下图,打开一个网页,我们发现它先刷出文本,过一会儿图片也出来了
- 如果是一个线程工作,它先用一段时间下载数据,下载好了之后突然很快的显示出来,如果是这样工作用户会感觉非常不爽,
- 应该怎么工作呢? 一个线程它先下载文本数据,下载完后切出去,切到显示文本的进程,显示完文本后再切回去继续下载图片…
- 为什么说这是一个线程而不是进程呢?
- 服务器从网上接受到的数据放在缓冲区里,显示文本的时候也要从缓冲区里取,
- 如果是进程的话,他们的地址是分离的,存和取都在不同的地方,就需要复制来复制去,就很麻烦,
- 他们本就是合作关系,完全不需要地址分离,他们完全可以共享数据,这就是为什么线程的原因
- 浏览器伪代码:
用Yield实现切换
- 一个栈
如下图,两个进程共用一个栈会不会有问题?我们走一遍程序
104;204;等代表压栈
- 首先执行A,A中调用了B,104入栈;
- 执行B,B中调用了Yield,204入栈;
- 执行Yield,pc设为300,jmp到300的地方也就是C;
- 执行C,调用了D,304入栈,;
- 执行D,调用Yield,404入栈;
- 执行Yield,因为这里是要切回去,所以找到的应该是204,pc设为204,jmp到B,
- 接下来遇到了B的 “}” ,此时该要ret弹出栈了,弹出的是404,我们发现又跑回D了,这显然不是我们想要的结果,B执行完本应该要回到A,结果却跑到D去了,
- 解决方法:用两个栈
- 两个栈
- 和上面一个栈不同的是Yield函数,这里用到了TCB线程控制块
- 执行Yield时,会切换栈,保证每个线程操作的是自己的栈
- 但是还是有问题
- 执行D的Yield时,切换栈(esp就是物理寄存器)和pc后,jmp到204,此时遇到B的“}”,改ret出栈了,弹出204,我们会发现又到204,
- 解决:去掉jmp 204;我们再从D的Yield走一遍,切换栈和pc后,此时遇到B的“}”,该ret出栈了,弹出204,又遇到B的“}”,ret出栈,这次弹出的是104…,这样就可以正常的执行了
- 总结:两个线程的样子:两个栈,两个pcb,切换的pc在栈中
ThreadCreate
用户级线程的缺点
* 用户级线程是用户态,当它getData时,会请求网卡读写,
* 网卡是硬件啊,要访问硬件,必须要经过操作系统所以此时会进入内核,
* 当网卡阻塞的时候,此时内核就会切换到其他进程去,因为 内核看不到外面有线程,它就会切到其他进程,
* 此时浏览器将会白白一片,什么都不显示
- 而核心级线程就不一样了
* 核心级线程的创建在内核中,要创建线程,就要进入内核,所以tcb也在内核中
* 这是执行getData时,假设阻塞了,但是缓冲区已经下载了一些数据,* * 此时可以切换到显示进程显示数据
* 所以内核级的线程并发性更好