简单理解超线程技术
超线程 (Hyper-Threading,简称"HT")“技术。超线程技术就是利用特殊的硬件指令,把两个逻辑内核模拟成两个物理芯片,让单个处理器都能使用线程级并行计算,进而兼容多线程操作系统和软件,减少了CPU的闲置时间,提高的CPU的运行效率。
超线程技术是在一颗CPU同时执行多个程序而共同分享一颗CPU内的资源,理论上要像两颗CPU一样在同一时间执行两个线程,虽然采用超线程技术能同时执行两个线程,但它并不象两个真正的CPU那样,每个CPU都具有独立的资源,当两个线程都同时需要某一个资源时,其中一个要暂时停止,并让出资源,直到这些资源闲置后才能继续。因此超线程的性能并不等于两颗CPU的性能。
如何查看有几个处理器?是否多核?是否超线程?
#逻辑CPU总个数:
grep -c processor /proc/cpuinfo
#物理CPU坐数:physical id
cat /proc/cpuinfo | grep "physical id" | sort | uniq
#单个物理CPU内核:cpu cores
grep 'cpu cores' /proc/cpuinfo
#单个物理CPU逻辑内核:siblings
grep 'siblings' /proc/cpuinfo
如果“siblings”和“cpu cores”一致,则说明不支持超线程,或者超线程未打开。
如果“siblings”是“cpu cores”的两倍,则说明支持超线程,并且超线程已打开。
【CPU物理核心数】
核心数为一个物理CPU拥有的运算单元数量,例如8核,即一个CPU有8个核心,拥有8个独立的运算单元。一个运算单元有一套寄存器,一般还有L1,L2的私有缓存,可以独立执行一个任务(一般是线程),所以一个核心同一时刻只能运行一个线程,不论这个线程是哪个进程的。每隔一定时间,大概几十毫秒,就会切换线程,即切换任务(可能是同一个进程的线程,也可能是另外一个进程的线程,如果是其它进程的线程,会切换CR3)。
为什么要切换CR3?
首先了解什么是CR3。每个进程都会有自己的一张分级的页表,用于查找该进程所访问到的物理内存(因为进程都是独立的,物理内存肯定是限制访问)。
当操作系统执行进程切换的时候,会将执行该进程页表的那个指针加载到CR3寄存器,这样当进程切换后,CPU访问的内存就是属于该进程的内存或者内核所使用的内存。进程对应的页表指针是由内核进行维护的。
当核心被切换到别的线程,并且这个线程属于别的进程,CR3必须更新为该线程所属的进程页表的那个指针,所以会切换CR3。我只简单理解为CR3用来标记线程所属的进程。
下面我们再说CPU的时候,直接用CORE核心来代替CPU的说法可能更明确。
【THREAD线程数(逻辑核心数)】
一般来说,一个物理核心就是一个独立的运算单元,一个物理核心(运算单元)同时最多运行一个线程,也就是一个线程数。但是英特尔的超线程技术,可以实现一个核心同时执行多个线程,则产生了逻辑核心数的说法:就是我们说的8核16线程,一个核心能跑二个线程,核心数是8,线程数就是16。
【超线程】
CPU拥有大量资源,并可以预先运行及平行运行指令,以增加指令运行效率,可是在现实中这些资源经常闲置,为了有效利用这些资源,就干脆再增加一些资源来运行第二个线程让这些闲置资源可执行另一个线程,而且CPU只要增加少数资源就可以模拟成两个线程运作,因此它可以实现真正的并行。
CPU在执行一条机器指令时,并不会完全地利用所有的CPU资源,而且实际上,是有大量资源被闲置着的。超线程技术允许两个线程同时不冲突地使用CPU中的资源。比如一条整数运算指令只会用到整数运算单元,此时浮点运算单元就空闲了,若使用了超线程技术,且另一个线程刚好此时要执行一个浮点运算指令,CPU就允许属于两个不同线程的整数运算指令和浮点运算指令同时执行,这是真的并行。
超线程"可能"是并行的,也可能不是并行的,但这也并不意味着两个线程在同一个CPU(也就是同一个核心)中一直都可以并行执行,只是恰好碰到两个线程当前要执行的指令不使用相同的CPU资源时才可以真正地并行执行。
[以上的CPU,就是指其中一个CORE核心]
下面我们再说线程的时候,就是指一个CORE核心运行一个线程的情况
【PROCESS进程】
操作系统分配资源的最小单位(包括CPU『这里的CPU指CPU核心』、内存、磁盘IO等)。一个进程可以拥有多个线程。
【THREAD线程】
线程是CPU调度和分配的基本单位。一个进程下的线程共享该进程的资源和程序代码。
【并发和并行】
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这说明你不支持并发也不支持并行。
并发:你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
并行:你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
它们最关键的点就是:是否是『同时』。
【单核CPU和多核CPU】
对于单核CPU,多个进程在单核CPU是并发运行,根据时间片读取上下文+执行程序+保存上下文。
对于多核CPU,多个进程在多核CPU是并行运行,进程能在多个核中运行。当然这时候单个核心也会存在进程切换。
总结:单核CPU中进程只能是并发,多核CPU中进程可以并行。
对于单核CPU,多个线程在单核CPU是并发运行。
对于多核CPU,多个线程在多核CPU是并行运行,线程能在多个核中运行。
总结:单核CPU中线程只能是并发,多核CPU中线程可以并行。
(上面说的单核CPU和多核CPU,又是指物理CPU;单核和多核指的是CORE核心数)
我们可以看到,对于线程和进程的描述是差不多一样的。
【线程是CPU调度和分配的基本单位】
有句话说CPU核心只能看到线程,可以这么理解:假设我是CORE核心,我闭着眼(等待进程调度),操作系统调度器将一个进程分配给我之后,我拿到进程睁开眼(分配到一个进程),开始工作时我看到的是什么?我看到的是进程中的很多线程和分配给我的资源,那么我现在能执行的是什么?进程?不行,因为我看不到其他进程(进程都是独立和隔离的),何来调度分配?
只能调度我看到的那些线程和资源,如果我是4核的话,把线程ABCD分配到核心1234,其他的线程依然要等待分配,至于等待多久,如何分配,暂不在讨论范围。
线程才是真正执行的单位,而执行任务的是CORE核心。于是有【线程是CPU调度和分配的基本单位】。也解释了【不论这个线程是哪个进程的,CPU眼里没有进程的概念这个说法】
说明:这里说的CORE核心被分配到一个进程,可以理解为CR3被切换到了一个新的进程,有了新的上下文,此时对于CORE核心来说,一般开始执行这个进程的主线程了。亦或者说操作系统调度了别的进程,开始执行新线程?这部分不太理解,后面再学习。
【进程是操作系统进行资源分配的最小单位】
线程和进程的关系是密切的,前面所说[线程是CPU调度和分配的基本单位],但是计算机资源是多种多样的(包括CPU『这里的CPU指CORE核心』、内存、磁盘IO等),因此操作系统在分配这些资源的时候,是以进程为单位进行分配。操作系统调度的也是进程。
【线程是CPU调度和分配的基本单位】一定要和【进程是操作系统进行资源分配的最小单位】区别清楚和理解
属于同个进程下的多个线程,是跑在不同的核上还是只能跑在同个核上?
在大多数支持多核的操作系统上,都能实现把一个进程的多个线程放不同的核心上跑。一个进程可能由多个线程组成,而线程的实现有两种方式,一种是内核调度的线程,可以运行在多个核上,还有一种是用户级线程,这个就没办法跑到多个核上了。(为什么用户级线程不能跑在多个核上?这个后面解释)
【COROUTINE协程】
要搞清楚协程,先明白【用户级线程】和【内核级线程】
又要先明白【用户态】和【内核态】
简单理解为:
【User Mode用户态】
运行用户程序,只能操作被分配的内存空间和资源,权限很小
【Kernel Mode内核态】
运行操作系统程序,操作硬件,权限很大
【用户级线程】
用户级线程是指不需要内核支持而在用户程序中实现的线程,它的内核的切换是由用户态程序自己控制内核的切换,不需要内核的干涉。但是它不能像内核级线程一样更好的运用多核CPU。
【内核级线程】
内核级线程是指切换由内核控制的线程,当线程进行切换的时候,由用户态转化为内核,切换完毕要从内核态返回用户态。
用户级线程的产生原因?
线程的概念出现了,但操作系统厂商可不能直接就去修改操作系统的内核,因为对他们来说,稳定性是最重要的。贸然把未经验证的东西加入内核,出问题了怎么办?所以想要验证线程的可用性,得另想办法。
研究人员就编写了一个关于线程的函数库,用函数库来实现线程。把创建线程、终止线程等功能放在了这个线程库内,用户就可以通过调用这些函数来实现所需要的功能。
刚刚我们说的线程库,是位于用户空间的,操作系统内核对这个库一无所知,对我们通过线程库创建的线程也一无所知。也就是说操作系统眼里还是只有这一个进程。
那我用线程库写的一个多线程进程,多个线程也就都只能在一个CPU核心上运行,无法运行在多核心上。这其实是用户级线程的一个缺点,这些线程只能占用一个核,所以做不到并行加速,而且由于用户线程的透明性,操作系统是不能主动切换线程的。
简单理解为:用户态线程是寄生在某个内核线程或者说寄生在某个进程上的,对于操作系统来说,它依然只是一个线程,当然只能运行在一个CORE核心上
解释了[为什么用户级线程不能跑在多个核上?]
[称为寄生不正确,但好描述,易理解]
这种模式同时造成一个严重问题:
如果A,B是同一个进程的两个线程的话,A正在运行的时候,线程B想要运行的话,只能等待A主动放弃CPU。
一个用户级线程的阻塞将会引起整个进程的阻塞,用户级线程执行系统调用指令时将导致其所属进程被中断,而内核级线程执行系统调用指令时,只导致该线程被中断,因为其他线程依然能跑在不同的核心上。
就是说,即使有线程库,用户级线程也做不到像内核级线程那样的轮转调度。
原因是:一个用户级线程阻塞了,在操作系统眼里,是进程阻塞了,那么整个进程就会进入阻塞态,在阻塞操作结束前,这个进程都无法得到CPU资源。
那就相当于,所有的用户级线程都被阻塞了。所以如果任由线程进行阻塞操作,进程的效率将受到很大的影响
解决办法:
用户级线程也有他自己的好处:你可以为你的应用程序定制调度算法,毕竟什么时候退出线程你自己说了算!!
这时候出现了一个办法:就是把一个产生阻塞的系统调用转化成一个非阻塞的系统调用!!
用户级线程再调用I/O,不是直接调用一个系统 I/O 例程,而是调用一个应用级别的 I/O 例程,这个例程中的代码会检查并且确定 I/O 设备是不是正忙
如果忙的话,就在用户态下将该线程阻塞,然后把控制权交给另一个线程。隔一段时间后再次检查 I/O 设备。
虽然最后还是会执行阻塞调用,但使用该方法可以缩短被阻塞的时间。不过有些情况下是可以不被阻塞的,取决于具体的实现。
在这个过程中,对于操作系统来说,这个内核线程或者说进程依然是执行的,不会被挂起。但是对于寄生在内核线程或者说进程上的用户级线程来说,它本身是阻塞了。在它阻塞时,把CORE核心让给其他用户级线程了,当然,这个用户态线程肯定是属于同个进程的。[称为寄生不正确,但好描述,易理解]
当然,我们可以创建非常多的内核线程,但是当线程数量非常多的时候,却产生了问题。一是系统线程会占用非常多的内存空间,二是过多的线程切换会占用大量的系统时间。
协程刚好可以解决上述2个问题。协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。
是不是看着很像?其实协程就是用户态线程。
这样理解进程,线程,协程就很好理解了:
一个进程有多个线程。
线程可能分为内核态线程和用户态线程。
【内核态线程】
优点是:一个进程的多个线程可以分配到多个CORE核心同时执行,并且由于内核级线程只有很小的数据结构和堆栈,切换速度快,本身也可以用多线程技术实现提高系统的运行速率。
缺点是:线程在用户态的运行,而线程的调度和管理在内核实现,在控制权从一个线程传送到另一个线程需要用户态到内核态再到用户态的模式切换,比较占用系统资源。
【用户态线程】
优点是:线程的调度不需要内核直接参与,控制简单,可以在不支持线程的操作系统中实现
缺点是:没有办法使用多核心并行执行