高并发专题(一)--基本概念和线程初识

什么是高并发

   &nbsp## 单核计算机发展的性能局限性
       著名的摩尔定律失效。当代的CPU效率基本定格在4GHz,这么意味着若是使用一个CUP在一台机器上,那么无论其他硬件多么优秀,瓶颈将出在CPU的计算能力。因此为了提升机器的性能,现在得计算机一般采用多CPU或超线程的方式来提升机器的性能。这样在同一时刻,机器的运算不能就不再局限于4GHz。超线程是指将CPU的逻辑内核模拟成物理芯片,从而达到并行执行的目的,提升机器的性能。超线程的并发能力并不是对CPU的能力做了100%的提升,只是相对于单核的话,有了很大的提升,这一点和真正的多核处理器是无法比较的。

并发和并行

       并发是指单位时间内需要运行的任务。注意,并发往往不代表一定在单位时间内运行完成,比如一个小网站突然受到黑客的攻击,一下子涌入几十万的请求,这个时候网站的服务器大概率会宕机。
       并行则是指同一时刻机器能处理几个任务。在单核CPU时代,人类的“并行”感受是因为人类的分辩能力,机器只需要在分辩时钟之内将同一事件继续执行,我们就会认为它是一直执行的,并没有停止。如执行任务ABCD。A->B->C->D->A。因为人类的分辩能力的原因,A在一瞬间又继续开始了,这样我们认为它是一直执行的。其实CPU会将时间细分到人类感觉分辩不出的级别,在每个时间片中执行不同的任务。并行则是真正的让多个任务同时执行。而非人类感官上的错觉。

线程和进程

       在众多教科书上说进程是计算机资源和程序的一种集合,是计算机分配资源的最小单元。
       线程是CPU调度的最小单元,因此线程只是用来争夺CPU的时间片的一种计算机资源。线程在java的世界中,是一种任务的体现,在并发的世界中,需要不停的同其他线程争抢CPU时间片来完成任务。线程的出现也是并发的基础,同时也是利用机器性能的有效手段。这样多核CPU就可以同时运行所需要的执行的多个任务,而不需要一个任务执行,其他CPU在等待任务的到来,造成CPU空闲资源浪费。

CPU和硬件在并发下的问题

       CPU是任务真正执行的地方,是机器的核心,但是机器是由众多组件共同组成的,目前来看,CPU是机器中运行速度会造成其他硬件处理CPU的指令来不及。这是典型的“木桶理论”。为此聪明的硬件工程师为大家提升性能想破了脑袋,实现了各级缓存,指令重排等一系列措施,来提升机器的性能。这些措施的目的是为了CPU在运行任务的时候不会因为其他的硬件处理指令过慢的原因导致CPU等待,而造成机器性能的浪费。
       各级缓存:从调查来看(什么研究调查,我也不知道,也不敢问),访问的数据很最近很短的时间内会再次被访问。因此smart的硬件工程师创建了缓存,为了平衡CPU和内存的运行速度不一致的问题,如果缓存有,就不用去内存中取了。而缓存的速度比内存快不少,因此会减少CPU和内存的交互,提升性能。但是数据的主体是在内存中,因此在多核CPU下,每个CPU的缓存中对主内存中同一数据做了写访问的时候,会造成数据安全性问题,任务A想把数据data改成数据D,而任务B则想把数据data改成数据E。结果是只有一个目的达成。另一任务做了无用功,而且很有可能告知我们,它完成了任务。
       指令重排:指令重排的最主要原因很多硬件的处理命令的速率不一。而,一个指令是多个硬件共同完成的,而不是一个硬件就能完成的。为了提升性能,smart的硬件工程师就想到了流水线的工作方式,我CPU负责发送指令的,不必要等一个指令执行完成再向大家发出另一步指令。举个栗子,把一个指令比喻组装成一个玩具汽车(需要装轮子,拼车身,贴纸),硬件A完成了装轮子,交给了B来拼车身,再交给C来贴纸,然后C交给CPU完成了一次指令工作。那么A完成需要等C,无疑会浪费计算机的性能。现在CPU聪明的不行,我把组装车子再细分,A完成后,我立即再给你一个,你就可以不停的装轮子了,而此时上一个指令还正在B那拼车身呢,而此时上上一个指令正在贴车纸呢。这对软件工程师就不太友好了,明明是先写的A,再写的B,为啥执行不是这样的呢?

指令重排基本规则

       虽然为了性能和效率的考量,硬件工程师采用了指令重排,但是为了安全问题和控制考虑,还是提供了一些规则来控制重排。
       ①程序顺序原则:单线程中最终结果一致,无歧义。
       ②锁规则:先加锁才能解锁。
       ③volatile变量规则:对于volatile变量写先于读操作。
       ④传递性:A先于B发生,B先于C发生,则A先于C发生。
       ⑤线程启动原则:线程执行逻辑一定是在其启动方法完成之后执行。
       ⑥线程终止原则:线程执行逻辑一定先于其终止逻辑执行。
       ⑦线程中断原则:线程中断事件方法一定先发生,才产生线程的中断。
       ⑧对象终止原则:对象创建的方法一定执行完成后才能执行finalize()方法。
       线程的几个原则严格的按照了传递性,启动>执行逻辑>终止。

线程的初识

       说了那么多,怎么让创建多个任务呢?在java的世界中,线程是CPU调度的基本单元。但是在PHP中,则会使用进程来处理并发。在java的API中,很多线程的方法都被标识为native,这意味着,很有可能java层面无法控制,需要底层的操作系统来实现。现在的操作系统实现线程分为三种模式。内核线程(1:1线程)、用户线程(1:N)、混合线程实现(N:M)。

内核线程

       内核线程(Kernel-Level Thread)就是由操作系统内核支持的线程。内核通过操作调度器来完成线程的调度以及将线程的任务映射到处理器上。但是程序一般不会使用内核线程,而是使用内核线程的一种高级接口——轻量级进程(Light Weight Process)。轻量级进程由一个内核线程支持(1:1关系)。
       它的缺点很明显。由于它是基于内核实现,所有的操作(创建,同步,解析等)都需要进行系统调用,而系统调用常常会在用户态和内核态之间来回切换,这样的消耗是很高的。而且内核资源是有限的,而轻量级进程会消耗一定的资源。
内核线程模型

用户线程

       广义上,只要线程不是内核线程就是用户线程(User Thread)。狭义上,系统内核是完全感知不到用户线程的存在以及操作的,所有的线程相关操作完全在用户态中完成。用户线程的一切操作是由程序内部实现,所以避免了大量的系统调用,避免了用户态和内核态的切换,因此效率比较高。
用户线程模型
       虽然用户线程在资源消耗上很少,但是在线程的调度,如进程阻塞,用户线程如何处理?还有如何把进程中多个线程映射到不同的CPU上进行并行执行呢?这些问题很复杂,甚至难以实现。现在很多标榜速度快的编程语言实现了此种线程诸如Golang,Erlang等。

混合实现

       混合实现,是将轻量级进程和用户线程进行混合。由用户线程来创建,切换,析构放在用户态,而线程和内核线程之间由轻量级进程作为桥梁,将线程任务映射等,交给轻量级线程和内核线程沟通。
混合线程模型
       混合模式的实现好处就线程的切换,析构,创建等在用户态中进行,降低资源的消耗,而且将任务的调度转给LWP来进行映射。降低了实现的复杂性。而且UT和LWP的实现关系并非一对一,而是N:M,对线程的阻塞等处理也有良好的处理。

用户态和内核态

       用户态和内核态最重要的区别在于权限。因此也称为特权状态和非特权状态。在核心态可以操作机器的一切资源,而用户态则不行。以此来保护机器,防止系统崩溃。
       一般有三种情况,程序会从用户态进入内核态中执行。
       ①系统调用,程序主动要求操作一些由系统提供的服务,此时程序进入内核态执行。
       ②系统中断,硬件设施通过信号触发,使用户程序转变为内核态,进行相应处理。
       ③异常处理,用户态程序出现异常,最终转变成内核态处理相关的异常信息。
       用户态和内核态的切换
       由于线程从用户态到内核态需要将用户空间中当前线程执行的所需要的数据指令copy到内核态,并且内核态会将当前的运行状态进行记录。所需要的消耗是不可忽视的。

java线程的实现

       在java的虚拟机规范中,并没有规范线程的实现方式,因此线程的实现主要是依据平台的不同来实现的。

线程的调度方式

       线程一般有两种调度方式,协同式和抢占式。
       协同式就是由执行线程来控制线程的调度,它在工作完成后会通知系统来进行线程的调度。这样危险性比较高,线程的执行完全由其自己控制,万一其他线程一直等不到机会执行程序就会导致很多问题。
       抢占式就是当前线程在执行的时候,可能CPU执行权(时间片)分配给了其他线程,当前线程就不得不暂停,等待CPU将时间片再次分配给自己。但是有些线程仍然需要多一点时间来完成工作,因此有了线程的优先级,但是各个操作系统所支持的优先级并不一样,因此通过优先级来设置线程的执行并不是一个合适的选择。

题外话:并发与协程

什么是协程

       早期用户线程采取的调度方式是协同式调度。因此也叫协程。协程采用的用户态,且调度采用的是协同式,因此是串行执行的,不需要加锁,而且相对于真正的线程,协程的资源开销是极其低下的。因为协程所有的操作都是在用户态,避免了与内核态之间的切换。而且线程所使用的栈是由程序自己去创建和维护的,他的栈大小通常只有几百个字节到几KB而已。

为什么需要协程

       早期web开发模式下,请求数量少,线程就满足了。但是随着互联网的发展,日益增大的流量,导致多线程在程序切换中浪费了大量的资源。而采用协程的时候,会降低线程的切换量级,从而达到更高效的目的。
       性能高的原因:协程是用户态的,他所需的运行环境完全由用户程序负责维护,而且协程在执行的时候是串行执行的,避免了数据竞争等问题。这样也就是为什么它的性能在高并发的情景下比较高效。
       协程并不适合在并发量不大的场景使用,因为在并发量小的时候,使用线程完全足够了处理。而协程是串行执行的。在一个线程中创建多个线程,同时也不能执行,而需排队执行。因此性能不如多线程并行执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值