承接上文CPU原理简介
程序的执行是由控制器发信号推动整个程序一步一步向前走,将数据存储在寄存器,从程序计数器中获取指令,比如先把3放到寄存器,再把5放到寄存器,再做一个加法,加法就是一个指令,从二进制的角度它也是01构成。
QQ.exe从硬盘读到内存里,内存里面装的全是二进制01,一个程序放到内存里面完全是由二进制01构成。
如何区分二进制01是指令还是数据?
二进制01可以把它看成一个指令,比如add指令,也可以看作是数据,比如5,如何区分这个二进制是指令还是数据?这是由IO Bridge(总线)来控制的,总线是从内存到CPU之间的一条数据线路。
总线分3种类型:控制线、地址线、数据线;从地址线读过来的就是地址,从控制线读过来的就是指令,从数据线读过来的就是数据。
地址线是由cpu中专门的地址寄存器来控制的,通过地址线去寻找地址,比如读到的是0101地址就去读取0101地址区域内存储的数字, 这个数字只要通过控制线过来的,就是指令加法,如果是数据线过来的就是数据5。
各干各自的事情,就看从哪里读过来的。
地址线是一个寻址的线,QQ.exe这个程序放到内存之后,它一定会放到内存的某个地址上,比如这个地址是3号地址,在3号地址的起始位置是main方法,在起步的位置一定会放一条指令,操作系统会通知cpu,你去读3号地址,先把指令读过来,读过来指令如果是add,它就会通知地址线,再把后面的2个数字都读过来,读到寄存器,然后计算单元开始做add计算,计算完了之后,存到某个寄存器,然后再写回内存。
QQ.exe双击之后放到内存里,一定会放到内存的某个地址上,比如内存地址从1号到1万号这么多的地址,比如把QQ.exe放3号地址,整个程序占用从3号到2000号地址区间,最开始的号一定是main方法所占的,最开始读的时候从3号地址开始读,操作系统通知cpu,我现在要运行一个程序,这个程序从3号到2000号,你现在帮我运行它,从3号地址开始读一条指令过来,所以通过控制总线读过来一条指令,这条指令是add,add指令的执行是需要数据的,需要2个数字相加,接下来操作系统会告诉你去哪个位置,比如去4号位置、5号位置去把那2个数字读过来,放到寄存器。将数据放到寄存器之后,运算单元去运算,运算完了之后,把结果放到某个寄存器,再把它写回到内存中的某个位置去,每个程序都有自己的地址空间,从虚拟内存到真正放到物理内存的时候需要做一个地址的映射。
一个程序的执行,首先把可执行文件放到内存,找到起始位置即main开始的地址,进行读取指令和数据,进行计算并写回内存。
什么是线程?什么是进程?
最开始的进程只能执行一个程序 ,必须等我把这个程序执行完,其他程序才可以执行。最开始的计算机只有一个cpu、一个计算单元,让它做程序的计算。先把自己的程序写到闪存卡上,插上去,按下按钮,计算机开始执行,计算机执行这个程序的时候,决对不会执行另外一个闪存卡,这个时候是单任务的。
执行3+5这段代码叫一个任务,这个任务放到计算机执行,只要它放到内存里面就称之为一个进程,只要有一个程序进入内存就可以称为一个进程;进程的概念反映到内存里,一个程序进入内存被称之为进程。
一个程序可以跑多个进程吗?
一个QQ.exe是可以运行多份的,一个程序本身是可以有多个进程的。
内存中的2个进程对应的程序是一个,随着程序的越来越复杂,人们发现不可能同时只一个进程。
2张闪存卡,就会有2个进程,但只有一个计算单元怎么能同时计算2个程序?
分时间计算即时间分片,同一个进程里面也可能要求2个不同的任务同时执行,比如一个带图形的界面,输入数字,服务端做计算,也要响应用户的输入,也可能正在接受网络传输的数据,即有多个任务在同时进行。
同一个进程内部有多个任务并发执行的需求,比如一边计算、一边接受网络数据、一边刷新界面,需要设计一种机制同时并发的运行,能不能把原来的一个进程分成3个进程?其实没有必要引入线程的概念,完全用多进程就行了,但进程有个严重的问题:每个进程都有自己地址空间,进程之间有同步和共享数据的过程,很容易出现一件事情,写我自己进程的时候很容易把另外的进程搞死,到此终于发明了线程。
多线程执行多个任务只使用了计算单元,并没有自己具体的地址空间,如果这3个任务想去访问数据的话,访问的是进程里面的同一份空间的数据即共享进程的空间,但是并不共享计算。
进程是静态的概念,一个程序只要放到内存,分配对应的资源主要是内存空间;线程是动态概念,可执行计算任务。
第一个进程进入内存之后,都会对应一个主线程,进程是分配资源用的,分配的资源是和存储、文件、网络相关的,最主要的是空间资源,进程都有自己的空间,线程是共享进程的空间。
多个线程访问的进程中的同一份数据,就会产生并发的问题。
线程是一条一条指令执行的,数据在进程里。协程是用户级别的线程。
为什么同一个代码可以被多个线程所执行?
线程是可执行的计算单元,做任务计算;
这颗cpu或这个计算单元正在执行这段代码,另外一个cpu正在执行的也是这段代码,2个线程,同一份代码即同一份指令,同一份指令怎么可以有多个线程在执行?
虽然是同一份指令,但有可能里面的数据是不一样的,每次执行参数都是不一样的,比如递归,第一次入参是n,第二次入参是n-1,即便参数是一样的,也可以同时执行2次,这里就会牵扯到线程切换的问题,
cpu正在执行t1,t1有它自己的指令和数据,这些数据是需要放到寄存器的,在t1没有执行完的时候,比如10多毫秒的时间片到了,切换到t2,t2执行完回来又轮到t1,t1不需要从头开始执行,要把t1执行到哪里了给记录下来,保存好上下文;t1执行到了哪条指令以及t1在寄存器是什么状态,全部放到一个缓存里,这个缓存是位于整个进程空间的,这叫保存现场,这就是线程切换的过程,但缓存宕掉了就会丢失,简单的理解为存储到了内存里,严格来讲是存到了锁存器中。
线程的切换,需要保存上下文(保存现场),是不是线程数量越多,执行效率越高?
一个线程正在等待网络的输入,但网络还没有输入,只有一个线程一个cpu的情况下就等着?这个时候显然需要把你的计算资源切换给其他人使用,所以中间有个切换的过程,10个线程可以切换,若有1万个活着的线程,操作系统必须保证每个线程都有时间来执行,不然就不叫活线程了 ,那就会把整个线程资源全部耗在线程切换上了。
对于一个程序或一个线程池设置多少个线程合适?线程池设定多少核心线程?
比如有1颗cpu,如果一个线程有50%的时间做计算,剩下的50%的时间等待着网络的输入和输出,在一颗cpu的情况下,多少个线程可以充分利用好这个cpu,答案是2个线程,在这个线程50%的时间计算完等待网络输入和输出的时候切换给另外一个线程使用。
根据线程等待的时间和计算时间的比值来计算所需的线程数,这里有一个理论上的计算公式,
Ucpu是期望cpu的利用率,比如期望cpu利用率是100%;
100% * (1+50% / 50%)=2个线程,1颗cpu 2个线程,2颗cpu就是4个线程。
当然这是理论,但实际中不能精确的计算出来多少时间做计算多少时间做wait ,最终还是要压测做决定。
这里面有个理想的情况 ,整个机器都被我这个进程所使用,其他进程不用,但是操作系统自己也有线程,其他正在跑着的程序自己也会有线程,那肯定会影响我这个进程,所以理论计算的结果会有一些偏差,实际中肯定是要做压测,可以通过理论计算出来一个初始值,比如10,将线程数设置为10个来做压测,看看是否达到期望值,再根据实际做调整。