询问线程的前生今生,三十二线程的升高历史及

作者: 网络编程  发布:2019-11-07

一.多线程的发展历史

一、了解进程、线程模型

多线程的发展大约经过了三个历史阶段: 1.最早出现的计算机主要是为了解决复杂的计算问题,而早期的计算机只能够接受一些特定的指令,当用户在输入这个指令的时候,计算机才会去工作,如果不输入指令,计算机就不会工作,因为计算机本身不会存储指令,很多情况下,计算机都会处于等待状态,并没有真正利用计算机本身的资源。于是进入了批处理操作系统的演变过程。 2.批处理操作系统:用户把需要执行的多个指令写在磁带上,然后让计算机去读取这个磁带执行相应的程序,并把结果输出在另外一个磁带上。 3.虽然批处理这种方式能大大提升计算机资源的利用率,但是会遇到一些问题,比如,操作系统的一个指令阻塞了,CPU会等到这个指令执行完毕后,再去执行下一个指令,这样的话就会使CPU处于等待状态,无法提高资源的利用率。为了解决这个问题,就出现了进程和线程的概念。

每次学习一个新技术,我会先去了解这个技术的背景,这个过程看似浪费时间,其实在后续的学习过程中,能够促进理解很多问题。所以对于线程这个概念,我会先从操作系统讲起。因为操作系统的发展带来了软件层面的变革。

欢迎加群 499754614学习交流,备注豆瓜。

从多线程的发展来看,可以操作系统的发展分为三个历史阶段:真空管和穿孔卡片晶体管和批处理系统集成电路和多道程序设计

二.进程与线程

进程 进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,系统会给每个进程分配独立的内存地址空间,并且每个进程的地址不会相互干扰。如果要完成CPU时间片的切换,就要保证之前的进程在执行的时候执行到某个位置,下次切换回来的时候仍然可以从这个位置开始执行。所以进程就是资源分配的最小单元。 在进程出现之前,指令是一次性加载到内存中,如果要进行指令切换的话,就要对指令进行隔离,而在批处理操作系统中是无法对指令进行隔离的。 有了进程以后,可以让操作系统从宏观上实现并发。并发是通过CPU时间片的不断切换执行的。在任意一个时刻,对于单核CPU来说,只会有一个任务去执行,只是通过切换时间片的方式完成了并行执行。 线程 线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,每个线程会负责一个独立的子任务,在配合多核处理器,去实现多个子任务并行处理的结果。线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多核CPU环境下就允许多个线程同时运行。进程在一个时间内只能干一件事情,如果想同时干多件事情的话, 就要把进程中的多个子任务划分到多个线程,通过线程的切换执行去实现任务的实时性。所以,线程是真正意义上实现了并行执行。

最早的计算机只能解决简单的数学运算问题,比如正弦、余弦等。运行方式:程序员首先把程序写到纸上,然后穿孔成卡票,再把卡片盒带入到专门的输入室。输入室会有专门的操作员将卡片的程序输入到计算机上。计算机运行完当前的任务以后,把计算结果从打印机上进行输出,操作员再把打印出来的结果送入到输出室,程序员就可以从输出室取到结果。然后,操作员再继续从已经送入到输入室的卡片盒中读入另一个任务重复上述的步骤。

三.多线程的使用场景

因为多线程最终解决的是“等待”的问题,所以多线程一般用于: 1.通过并行计算提高程序执行的性能,比如一个程序中的计算逻辑的执行性能可以通过多线程的技术将一个程序中的多个逻辑运算并行操作执行。 2.需要等待网络,IO响应等耗费大量的时间,可以采用异步的方式来减少总体的响应时间,也就是解决阻塞(当程序运行到某个函数时,由于一些原因导致程序要等待某个事件的发生而暂时停止占用CPU)的问题,阻塞会使CPU闲置而浪费资源。

操作员在机房里面来回调度资源,造成计算机存在大量的空闲状态 。而当时的计算机是非常昂贵的,人们为了减少这种资源的浪费。就采用了 批处理系统来解决

批处理操作系统的运行方式:在输入室收集全部的作业,然后用一台比较便宜的计算机把它们读取到磁带上。然后把磁带输入到计算机,计算机通过读取磁带的指令来进行运算,最后把结果输出磁带上。批处理操作系统的好处在于,计算机会一直处于运算状态,合理的利用了计算机资源。(运行流程如下图所示)

图片 1

P7架构师带你深入了解线程的发展历史

(注:此图来源于现代操作系统)

批处理操作系统虽然能够解决计算机的空闲问题,但是当某一个作业因为等待磁盘或者其他I/O操作而暂停,那CPU就只能阻塞直到该I/O完成,对于CPU操作密集型的程序,I/O操作相对较少,因此浪费的时间也很少。但是对于I/O操作较多的场景来说,CPU的资源是属于严重浪费的。

多道程序设计的出现解决了这个问题,就是把内存分为几个部分,每一个部分放不同的程序。当一个程序需要等待I/O操作完成时。那么CPU可以切换执行内存中的另外一个程序。如果内存中可以同时存放足够多的程序,那CPU的利用率可以接近100%。

在这个时候,引入了第一个概念-进程, 进程的本质是一个正在执行的程序,程序运行时系统会创建一个进程,并且给每个进程分配独立的内存地址空间保证每个进程地址不会相互干扰。同时,在CPU对进程做时间片的切换时,保证进程切换过程中仍然要从进程切换之前运行的位置出开始执行。所以进程通常还会包括程序计数器、堆栈指针。

有了进程以后,可以让操作系统从宏观层面实现多应用并发。而并发的实现是通过CPU时间片不端切换执行的。对于单核CPU来说,在任意一个时刻只会有一个进程在被CPU调度

有了进程以后,为什么还会出现线程呢?

在一个应用进程中,会存在多个同时执行的任务,如果其中一个任务被阻塞,将会引起不依赖该任务的任务也被阻塞。举个具体的例子来说,我们平常用word文档编辑内容的时候,都会有一个自动保存的功能,这个功能的作用是,当计算机出现故障的情况下如果用户未保存文档,则能够恢复到上一次自动保存的点。假设word的自动保存因为磁盘问题导致写入较慢,势必会影响到用户的文档编辑功能,直到磁盘写入完成用户才可编辑,这种体验是很差的。如果我们把一个进程中的多个任务通过线程的方式进行隔离,那么按照前面提到的进程演进的理论来说,在单核心CPU架构中可以通过CPU的时间片切换实现线程的调度充分利用CPU资源以达到最大的性能。

我们用了比较长的篇幅介绍了进程、线程发展的历史。总的来说是人们对于计算机的要求越来越高;对于计算机本身的资源的利用率也在不断提高。

二、线程的优势

前面分析了线程的发展历史,这里简单总结一下线程有的优势如下线程可以认为是轻量级的进程,所以线程的创建、销毁要比进程更快从性能上考虑,如果进程中存在大量的I/O处理,通过多线程能够加快应用程序的执行速度(通过CPU时间片的快速切换)。由于线程是CPU的最小调度单元,所以在多CPU架构中能够实现真正的并行执行。每一个CPU可以调度一个线程

并行:同时执行多个任务,在多核心CPU架构中,一个CPU核心运行一个线程,那么4核心CPU,可以同时执行4个线程

并发:同处理多个任务的能力,通常我们会通过TPS或者QPS来表示某某系统支持的并发数是多少。

总的来说,并行是并发的子集。也就是说我们可以写一个拥有多线程并行的程序,如果在没有多核心CPU来执行这些线程,那就不能以并行的方式来运行程序中的多个线程。所以并发程序可以是并行的,也可以不是。Erlang之父Joe Armstrong通过一张图型的方式来解释并发和并行的区别,图片如下

图片 2

P7架构师带你深入了解线程的发展历史

三、线程的生命周期

线程是存在生命周期的,从线程的创建到销毁,可能会经历6种不同的状态,但是在一个时刻线程只能处于其中一种状态NEW:初始状态,线程被创建时候的状态,还没有调用start方法RUNNABLE:运行状态,运行状态包含就绪和运行两种状态,因为线程启动以后,并不是立即执行,而是需要通过调度去分配CPU时间片BLOCKED:阻塞状态,当线程去访问一个加锁的方法时,如果已经有其他线程获得锁,那么当前线程会处于阻塞状态WAITING:等待状态,设置线程进入等待状态等待其他线程做一些特定的动作进行触发TIME_WAITING:超时等待状态,和WAITING状态的区别在于超时以后自动返回TERMINATED:终止状态,线程执行完毕

下图整理了线程的状态变更过程及变更的操作,每一个具体的操作原理,我会在后续的文章中进行详细分析。

图片 3

P7架构师带你深入了解线程的发展历史

这里有一个问题大家可能搞不明白,BLOCKED和WAITING这两个阻塞有什么区别?BLOCKED状态是指当前线程在等待一个获取锁的操作时的状态。WAITING是通过Object.wait或者Thread.join、LockSupport.park等操作实现的BLOCKED是被动的标记,而WAITING是主动操作如果说得再深入一点,处于WAITING状态的线程,被唤醒以后,需要进入同步队列去竞争锁操作,而在同步队列中,如果已经有其他线程持有锁,则线程会处于BLOCKED状态。所以可以说BLOCKED状态是处于WAITING状态的线程重新唤醒的必经的状态

四、线程的应用场景

线程的出现,在多核心CPU架构下实现了真正意义上的并行执行。也就是说,一个进程内多个任务可以通过多线程并行执行来提高程序运行的性能。那线程的使用场景有哪些呢?执行后台任务,在很多场景中,可能会有一些定时的批量任务,比如定时发送短信、定时生成批量文件。在这些场景中可以通过多线程的来执行异步处理,比如在用户注册成功以后给用户发送优惠券或者短信,可以通过异步的方式来执行,一方面提升主程序的执行性能;另一方面可以解耦核心功能,防止非核心功能对核心功能造成影响分布式处理,比如fork/join,将一个任务拆分成多个子任务分别执行BIO模型中的线程任务分发,也是一种比较常见的使用场景,一个请求对应一个线程

合理的利用多线程,可以提升程序的吞吐量。同时,还可以通过增加CPU的核心数来提升程序的性能,这就体现了伸缩性的特点

本文由王中王开奖结果发布于网络编程,转载请注明出处:询问线程的前生今生,三十二线程的升高历史及

关键词: