本篇全面学习多线程相关的知识,首先从基础概念开始,然后一步一步深入并学习源码。
以下内容主要来自百度百科词条。
进程
以下进程的内容完全来自百度百科:(如果太长不想看的话可以一句话理解进程: 进程是正在运行的程序的实例,当一个程序进入内存运行时,即成为了一个进程,在
iOS
中,一个App
的启动就是开启一个进程。)
进程(Process
)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存,是操作系统结构的基础。
在早期面向进程设计的计算机机构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。
定义
- 狭义定义:进程是正在运行的程序的实例。(当一个程序进入内存运行时,即成为了一个进程。在
iOS
中,一个App
的启动就是开启一个进程。) - 广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region
)、数据区域(data region
)和堆栈(stack region
)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
多道程序系统是在计算机内存中同时存放几道相互独立的程序,使它们在管理程序控制之下,相互穿插的运行。两个或两个以上程序在计算机系统中同处于开始和结束之间的状态。这就称为多道程序技术运行的特征:多道、宏观上并发、微观上串行。
引入进程的原因
操作系统引入进程的概念的原因:
- 从理论角度看,是对正在运行的程序过程的抽象。
- 从实现角度看,是一种数据结构,目的在于清晰地刻画动态系统的内在规律,有效管理和调度进入计算机系统主存储器运行的程序。
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这里有一个iOS交流群:[891488181]不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
进程的特征
- 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
- 并发性:任何进程都可以同其他进程一起并发执行。
- 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
- 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进。
- 结构特征:进程由程序、数据和进程控制块三部分组成。
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。
进程控制块
进程控制块(Processing Control Block
),是操作系统核心中一种数据结构,主要表示进程状态。其作用是使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能独立运行的基本单位或与其它进程并发执行的进程。或者说,OS 是根据 PCB 来对并发执行的进程进行控制和管理的。 PCB 通常是系统内存占用区中的一个连续存区,它存放着操作系统用于描述进程情况及控制进程运行所需的全部信息,它使一个在多道程序环境下不能独立运行的程序成为一个能独立运行的基本单位或一个能与其他进程并发执行的进程。
基本内容
进程控制块(PCB
)是系统为了管理进程设置的一个专门的数据结构。系统用它来记录进程的外部特征,描述进程的运动变化过程。同时,系统可以利用 PCB 来控制和管理进程,所以说,PCB(进程控制块)是系统感知进程存在的唯一标志。
记载信息
PCB 通常记载进程相关信息,包括:
- 程序计数器:接着要运行的指令地址。
- 进程状态:可以是 new、ready、running、waiting 或 blocked等。
- CPU 暂存器:如累加器、索引暂存器(Index register)、堆栈指针以及一般用途暂存器、状况代码等,主要用途在于中断时暂时存储数据,以便稍后继续利用;其数量及类因电脑架构有所差异。
- CPU 排班法:优先级、排班队列等指针以及其他参数。
- 存储器管理:如标签页表等。
- 会计信息:如 CPU 与实际时间之使用数量、时限、账号、工作或进程号码。
- 输入输出状态:配置进程使用 I/O 设备,如磁带机。
组织方式
进程控制块 PCB 的组织方式
- 线性表方式:不论进程的状态如何,将所有的 PCB 连续地存放在内存的系统区。这种方式适用于系统中进程数目不多的情况。
- 索引表方式:该方式是线性表方式的改进,系统按照进程的状态分别建立就绪索引表、阻塞索引表等。
- 链接表方式:系统按照进程的状态将进程的 PCB 组成队列,从而形成就绪队列、阻塞队列、运行队列等。
内容
一个计算机系统进程包括(或者说“拥有”)下列数据: 那个程序的可运行机器码的一个在存储器的映像。分配到的存储器(通常包括虚拟内存的一个区域)。存储器的内容包括可运行代码、特定于进程的数据(输入、输出)、调用堆栈、堆栈(用于保存运行时运数中途产生的数据)。分配给该进程的资源的操作系统描述符,诸如文件描述符(Unix
术语)或文件句柄(Windows
)、数据源和数据终端。安全特性,诸如进程拥有者和进程的权限集(可以容许的操作)。处理器状态(内文),诸如寄存器内容、物理存储器寻址等。当进程正在运行时,状态通常储存在寄存器,其他情况在存储器。
切换
进行进程切换就是从正在运行的进程中收回处理器,然后再使运行进程来占用处理器。
这里所说的从某个进程收回处理器,实质上就是把进程存放在处理器的寄存器中的中间数据找个地方存起来,从而把处理器的寄存器腾出来让其它进程使用。那么被中止运行进程的中间数据存在何处好呢?当然这个地方应该是进程的私有堆栈。
让进程来占用处理器,实质上是把某个进程存放在私有堆栈中寄存器的数据(前一次本进程被中止时的中间数据)再恢复到处理器的寄存器中去,并把待运行进程的断点送入处理器的程序指针 PC
(PC
寄存器),于是待运行进程就开始被处理器运行了,也就是这个进程已经占有处理器的使用权了。
这就像多个同学要分时使用同一张课桌一样,所谓要收回正在使用课桌同学的课桌使用权,实质上就是让他把属于他的东西拿走;而赋予某个同学课桌使用权,只不过就是让他把他的东西放到课桌上罢了。
在切换时,一个进程存储在处理器各寄存器中的中间数据叫做进程的上下文,所以进程的切换实质上就是被中止运行进程与待运行进程上下文的切换。在进程未占用处理器时,进程的上下文是存储在进程的私有堆栈中的。
状态
进程执行时的间断性,决定了进程可能具有多种状态。事实上,运行中的进程可能具有以上三种基本状态。
- 就绪状态(
Ready
):
进程已获得除处理器外的所需资源,等待分配处理器资源;只要分配了处理器进程就可执行。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由 I/O 操作完成而进入就绪状态时,排入高优先级队列。
- 运行状态(
Running
):
进程占用处理器资源;处于此状态的进程的数目小于等于处理器的数目。在没有其它进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。
- 阻塞状态(
Blocked
):
由于进程等待某种条件(如 I/O 操作或进程同步),在条件满足之前无法继续执行。该事件发生前即使把处理器资源分配给该进程,也无法运行。
区别
处理机:处理机包括中央处理器,主存储器,输入-输出接口,加接外围设备就构成完整的计算机系统。处理机是处理计算机系统中存储程序和数据,并按照程序规定的步骤执行指令的部件。程序是描述处理机完成某项任务的指令序列。指令则是处理机能直接解释、执行的信息单位。
程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。 程序可以作为一种软件资料长期存在,而进程是有一定生命周期的。程序是永久的,进程是暂时的。 进程更能真实的描述并发,而程序不能。 进程是由进程控制块、程序段、数据段三部分组成。 进程具有创造其它进程的功能,而程序没有。 同一个程序同时运行于若干个数据集合上,它将属于若干个不同的进程,也就是说同一程序可以对应多个进程。 在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单元都是进程。
线程:通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小的多,能更高效的提高系统内多个程序间并发执行的程度。 当下推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。
控制
进程控制是进程管理中最基本的功能。它用于创建一个新进程,终止一个已完成的进程,或者去终止一个因出现某事件而使其无法运行下去的进程,还可负责进程运行中的状态转换。
创建进程
- 引起创建进程的事件
在多道程序环境中,只有(作为)进程(时)才能在系统中运行。因此,为使程序能运行,就必须为它创建进程。导致一个进程去创建另一个进程的典型事件,可以有以下四类:
- 用户登录
在分时系统中,用户在终端键入登录命令后,如果是合法用户,系统将为该终端建立一个进程,并把它插入到就绪队列中。
- 作业调度
在批处理系统中,当作业调度程序按照一定的算法调度到某作业时,便将该作业装入到内存,为它分配必要的资源,并立即为它创建进程,再插入到就绪队列中。
- 提供服务
当运行中的用户程序提出某种请求后,系统将专门创建一个进程来提供用户所需的服务,例如,用户程序要求进行文件打印,操作系统将为它创建一个打印进程,这样,不仅可以使打印进程与该用户进程并发执行,而且还便于计算出为完成打印任务所花费的时间。
- 应用请求
在上述三种情况中,都是由系统内核为它创建一个新进程,而这一类事件则是基于应用进程的需求,由它创建一个新的进程,以便使新进程以并发的运行方式完成特定任务。
- 进程的创建过程
原语-计算机进程的控制通常由原语完成。所谓原语,一般是指由若干条指令组成的程序段,用来实现某个特定功能,在执行过程中不可被中断。在操作系统中,某些被进程调用的操作,如队列操作、对信号量的操作、检查启动外设操作等,一旦开始执行,就不能被中断,否则就会出现操作错误,造成系统混乱。所以,这些操作都要用原语来实现 原语是操作系统核心(不是由进程,而是由一组程序模块组成)的一个组成部分,并且常驻内存,通常在管态下执行。原语一旦开始执行,就要连续执行完,不允许中断。
一旦操作系统发现了要求创建新进程的事件后,便调用进程创建原语 create()
按下述步骤创建一个新进程。
- 申请空白
PCB
。为新进程申请获得唯一的数字标识符,并从PCB
集合中索取一个空白PCB
。 - 为新进程分配资源。为新进程的程序和数据以及用户栈分配必要的内存空间。显然,此时操作系统必须知道新进程所需要的内存大小。
- 初始化进程控制块。
PCB
的初始化包括:
(1): 初始化标识信息,将系统分配的标识符和父进程标识符,填入新的 PCB
中。 (2): 初始化处理机状态信息,使程序计数器(PC
)指向程序的入口地址,使栈指针指向栈顶。 (3): 初始化处理机控制信息,将进程的状态设置为就绪状态或静止就绪状态,对于优先级,通常是将它设置为最低优先级,除非用户以显式的方式提出高优先级要求。
- 将新进程插入就绪队列,如果进程就绪队列能够接纳新进程,便将新进程插入到就绪队列中。
进程终止
- 引起进程终止的事件
- 正常结束
在任何计算机系统中,都应该有一个表示进程已经运行完成的指示。例如,在批处理系统中,通常在程序的最后安排一条 Hold
指令或终止的系统调用。当程序运行到 Hold
指令时,将产生一个中断,去通知 OS 本进程已经完成。
- 异常结束
在进程运行期间,由于出现某些错误和故障而迫使进程终止。这类异常事件很多,常见的有:越界错误、保护错、非法指令、特权指令错、运行超时、等待超时、算术运算错、I/0 故障。
- 外界干预
外界干预并非指在本进程运行中出现了异常事件,而是指进程应外界的请求而终止运行。这些干预有:操作员或操作系统干预、父进程请求、父进程终止。
- 进程的终止过程
如果系统发生了上述要求终止进程的某事件后,OS 便调用进程终止原语,按下述过程去终止指定的进程。
- 根据被终止进程的标识符,从
PCB
集合中检索出该进程的PCB
,从中读出该进程状态。 - 若被终止进程正处于执行状态,应立即终止该进程的执行,并置调度标志为真。用于指示该进程被终止后应重新进行调度。
- 若该进程还有子孙进程,还应将其所有子孙进程予以终止,以防他们成为不可控的进程。
- 将被终止的进程所拥有的全部资源,或者归还给其父进程,或者归还给系统。
- 将被终止进程(它的
PCB
)从所在队列(或链表)中移出,等待其它程序来搜集信息。
阻塞唤醒
- 引起进程阻塞和唤醒的事件
- 请求系统服务
当正在执行的进程请求操作系统提供服务时,由于某种原因,操作系统并不立即满足该进程的要求时,该进程只能转变为阻塞状态来等待,一旦要求得到满足后,进程被唤醒。
- 启动某种操作
当进程启动某种操作后,如果该进程必须在该操作完成之后才能继续执行,则必须先使该进程阻塞,以等待该操作完成,该操作完成后,将该进程唤醒。
- 新数据尚未到达
对于相互合作的进程,如果其中一个进程需要先获得另一(合作)进程提供的数据才能运行以对数据进行处理,则是要其所需数据尚未到达,该进程只有(等待)阻塞,等到数据到达后,该进程被唤醒。
- 无新工作可做
系统往往设置一些具有某特定功能的系统进程,每当这种进程完成任务后,便把自己阻塞起来以等待新任务到来,新任务到达后,该进程被唤醒。
- 进程阻塞过程
正在执行的进程,当发现上述某事件后,由于无法继续执行,于是进程便通过调用阻塞原语 block()
把自己阻塞。可见,进程的阻塞是进程自身的一种主动行为。进入 block
过程后,由于此时刻该进程还处于执行状态,所以应先立即停止执行,把进程控制块中的现行状态由执行改为阻塞,并将 PCB
插入阻塞队列。如果系统中设置了因不同事件而阻塞的多个阻塞队列,则应将本进程插入到具有相同事件的阻塞(等待)队列。最后,转调度程序进行重新调度,将处理机分配给另一就绪进程,并进行切换,亦即,保留被阻塞进程的处理机状态(在 PCB
中),再按新进程的 PCB
中的处理机状态设置 CPU
环境。
- 进程唤醒的过程
当被阻塞的进程所期待的事件出现时,如 I/O 完成或者其所期待的数据已经到达,则由有关进程(比如,用完并释放了该 I/O 设备的进程)调用唤醒原语 weakup()
,将等待该事件的进程唤醒。唤醒原语执行的过程是:首先把被阻塞的进程从等待该事件的阻塞队列中移出,将其 PCB
中的现行状态由阻塞改为就绪,然后再将该 PCB
插入到就绪队列中。
调度算法
进程的调度算法包括: 实时系统中:FIFO
(First Input First Output
,先进先出算法),SJF
(Shortest Job First
,最短作业优先算法),SRTF
(Shortest Remaining Time First
,最短剩余时间优先算法)。 交互式系统中:RR
(Round Robin
,时间片轮转算法),HPF
(Highest Priority First
,最高优先级算法),多级队列,最短进程优先,保证调度,彩票调度,公平分享调度。
阶段
进程是由进程控制块、程序段、数据段三部分组成。一个进程可以包含若干线程(Thread
),线程可以帮助应用程序同时做几件事(比如一个线程向磁盘写入文件,另一个则接收用户的按键操作并及时做出反应,互不干扰),在程序被运行后,系统首先要做的就是为该程序进程建立一个默认线程(在 iOS
中 App
启动会默认为我们开启一条主线程),然后程序可以根据需要自行添加或删除相关的线程。是可并发执行的程序。在一个数据集合上的运行过程,是系统进行资源分配和调度的一个独立单位,也是称活动、路径或任务,它有两方面性质:活动性、并发性。进程可以划分为运行、阻塞、就绪三种状态,并随一定条件而相互转化:就绪-运行,运行-阻塞,阻塞-就绪。
进程为应用程序的运行实例,是应用程序的一次动态执行。看似高深,我们可以简单的理解为:它是操作系统当前运行的执行程序。在系统当前运行的执行程序里包括:系统管理计算机个体和完成各种操作所必需的程序;用户开启、执行的额外程序,当然也包括用户不知道,而自动运行的非法程序(它们就有可能是病毒程序)。
线程
线程(thread
)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在 Unix System V
及 SunOS
中也被称为轻量进程(lightweight processes
),但轻量进程更多指内核线程(kernel thread
),而把用户线程(user thread
)称为线程。
线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如 Win32
线程;由用户进程自行调度的用户线程,如 Linux
平台的 POSIX Thread
;或者由内核与用户进程,如 Windows 7
的线程,进行混合调度。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack
),自己的寄存器环境(register context
),自己的线程本地存储(thread-local storage
)。
一个进程可以有很多线程,每条线程并行执行不同的任务。
在多核或多 CPU
,或支持 Hyper-threading
的 CPU
上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单 CPU
单核的计算机上,使用多线程技术,也可以把进程中负责 I/O
处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的 workhorse
线程执行密集计算,从而提高了程序的执行效率。
发展简史
线程的引入:60 年代,在 OS 中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤销与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对程多处理机(SMP
)出现,可以满足多个运行单位,而多个进程并行开销过大。 因此在 80 年代,出现了能独立运行的基本单位--线程(thread
)。
适用范围
- 服务器中的文件管理或通信控制。
- 前后台处理。
- 异步处理。
特点
在多线程 OS 中,通常是在一个进程中包括多个线程,每个线程都是作为利用 CPU
的基本单位,是花费最小开销的实体。线程具有以下属性。
- 轻型实体
线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。线程的实体包括程序、数据和 TCB
。线程是动态概念,它的动态特性由线程控制块 TCB
(Thread Control Block
) 描述。 TCB
包括以下信息:
- 线程状态。
- 当线程不运行时,被保存的现场资源。
- 一组执行堆栈。
- 存放每个线程的局部变量主存区。
- 访问同一个进程中的主存和其它资源。
用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。 2. 独立调度和分派的基本单位。 在多线程 OS 中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中)。 3. 可并发执行。 在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。 4. 共享进程资源。 在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
与进程比较
进程是资源分配的基本单位。所有与该进程相关的资源,都被记录在进程控制块 PCB
中。以表示该进程拥有这些资源或正在使用它们。 另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一个地址空间。 与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。 线程只由相关堆栈(系统栈或用户栈)寄存器和线程控制表 TCB
组成。寄存器可被用来存储线程内的局部变量,但不能存储其他线程的相关变量。 通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小的多,能更高效的提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量。因而进年来推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。 线程与进程的区别可以归纳为以下 4 点:
- 地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
- 通信:进程间通信 IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信--需要进程同步和互斥手段的辅助,以保证数据的一致性。
- 调度和切换:线程上下文切换比进程上下文切换要快得多。
- 在多线程 OS 中,进程不是一个可执行的实体。
线程的同步
线程的同步是多线程编程的难点,往往开发者搞不清楚什么是竞争资源、什么时候需要考虑同步,怎么同步等等问题,当然,这些问题没有很明确的答案,但有些原则问题需要考虑,是否有竞争资源被同时改动的问题?
对于同步,在具体的 Java 代码中需要完成以下两个操作:把竞争访问的资源标识为 private;同步那些修改变量的代码,使用 synchronized 关键字同步方法或代码。当然这不是唯一控制并发安全的途径。synchronized 关键字使用说明 synchronized 只能标记非抽象的方法,不能标识成员变量。
工作原理
线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程不拥有系统资源,只有运行必须的一些数据结构;它与父进程的其它线程共享该进程所拥有的全部资源。线程可以创建和撤消线程,从而实现程序的并发执行。一般,线程具有就绪、阻塞和运行三种基本状态。
在多中央处理器的系统里,不同线程可以同时在不同的中央处理器上运行,甚至当它们属于同一个进程时也是如此。大多数支持多处理器的操作系统都提供编程接口来让进程可以控制自己的线程与各处理器之间的关联度(affinity)。
有时候,线程也称作轻量级进程。就象进程一样,线程在程序中是独立的、并发的执行路径,每个线程有它自己的堆栈、自己的程序计数器和自己的局部变量。但是,与分隔的进程相比,进程中的线程之间的隔离程度要小。它们共享内存、文件句柄和其它每个进程应有的状态。
进程可以支持多个线程,它们看似同时执行,但互相之间并不同步。一个进程中的多个线程共享相同的内存地址空间,这就意味着它们可以访问相同的变量和对象,而且它们从同一堆中分配对象。尽管这让线程之间共享信息变得更容易,但您必须小心,确保它们不会妨碍同一进程里的其它线程。
线程的操作(线程的状态)
派生:线程在进程内派生出来,它即可由进程派生,也可由线程派生。 阻塞(Block
):如果一个线程在执行过程中需要等待某个事件发生,则被阻塞。 激活(unblock
):如果阻塞线程的事件发生,则该线程被激活并进入就绪队列。 调度(schedule
):选择一个就绪线程进入执行状态。 结束(Finish
):如果一个线程执行结束,它的寄存器上下文以及堆栈内容等将被释放。
线程优先级
虽然我们说线程是并发运行的。然而事实常常并非如此。正如前面谈到的,当系统中只有一个CPU时,以某种顺序在单CPU情况下执行多线程被称为调度(scheduling
)。Java 采用的是一种简单、固定的调度法,即固定优先级调度。这种算法是根据处于可运行态线程的相对优先级来实行调度。当线程产生时,它继承原线程的优先级。在需要时可对优先级进行修改。在任何时刻,如果有多条线程等待运行,系统选择优先级最高的可运行线程运行。只有当它停止、自动放弃、或由于某种原因成为非运行态低优先级的线程才能运行。如果两个线程具有相同的优先级,它们将被交替地运行。 Java实时系统的线程调度算法还是强制性的,在任何时刻,如果一个比其他线程优先级都高的线程的状态变为可运行态,实时系统将选择该线程来运行。一个应用程序可以通过使用线程中的方法setPriority(int),来设置线程的优先级大小。 有线程进入了就绪状态,需要有线程调度程序来决定何时执行,根据优先级来调度。
线程状态变化
- 创建线程:当创建一个新的进程时,也创建一个新的线程,进程中的线程可以在同一进程中创建新的线程。
- 终止线程:可以正常终止自己,也可能某个线程执行错误,由其它线程强行终止。终止线程操作主要负责释放线程占有的寄存器和栈。
- 阻塞线程:当线程等待每个事件无法运行时,停止其运行。
- 唤醒线程:当阻塞线程的事件发生时,将被阻塞的线程状态置为就绪态,将其挂到就绪队列。进程仍然具有与执行相关的状态。例如,所谓进程处于“执行” 状态,实际上是指该进程中的某线程正在执行。对进程施加的与进程状态有关的操作,也对其它线程起作用。例如,把某个进程挂起时,该进程中的所有线程也都被挂起,激活也是同样。
线程池
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。 线程池(thread pool
):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络 sockets 等的数量。 例如,线程数一般取 CPU 数量 +2 比较合适,线程数过多会导致额外的线程切换开销。
任务调度以执行线程的常见方法是使用同步队列,称作任务队列。池中的线程等待队列中的任务,并把执行完的任务放入完成队列中。
多线程
多线程(multithreading
),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作 “线程”(Thread
),利用它编程的概念就叫作“多线程处理”。
简介
在计算机编程中,一个基本的概念就是同时对多个任务加以控制。许多程序设计问题都要求程序能够停下手头的工作,改为处理其他一些问题,再返回主进程。可以通过多种途径达到这个目的。最开始的时候,那些掌握机器低级语言的程序员编写一些“中断服务例程”,主进程的暂停是通过硬件级的中断实现的。尽管这是一种有用的方法,但编出的程序很难移植,由此造成了另一类的代价高昂问题。中断对那些实时性很强的任务来说是很有必要的。但对于其他许多问题,只要求将问题划分进入独立运行的程序片断中,使整个程序能更迅速地响应用户的请求。
最开始,线程只是用于分配单个处理器的处理时间的一种工具。但假如操作系统本身支持多个处理器,那么每个线程都可分配给一个不同的处理器,真正进入 “并行运算”状态。从程序设计语言的角度看,多线程操作最有价值的特性之一就是程序员不必关心到底使用了多少个处理器。程序在逻辑意义上被分割为数个线程;假如机器本身安装了多个处理器,那么程序会运行得更快,毋需作出任何特殊的调校。
根据前面的论述,大家可能感觉线程处理非常简单。但必须注意一个问题:共享资源!如果有多个线程同时运行,而且它们试图访问相同的资源,就会遇到一个问题。举个例子来说,两个线程不能将信息同时发送给一台打印机。为解决这个问题,对那些可共享的资源来说(比如打印机),它们在使用期间必须进入锁定状态。所以一个线程可将资源锁定,在完成了它的任务后,再解开(释放)这个锁,使其他线程可以接着使用同样的资源。
多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。
原理
实现多线程是采用一种并发执行机制。并发执行机制原理:简单地说就是把一个处理器划分为若干个短的时间片,每个时间片依次轮流地执行处理各个应用程序,由于一个时间片很短,相对于一个应用程序来说,就好像是处理器在为自己单独服务一样,从而达到多个应用程序在同时进行的效果。 多线程就是把操作系统中的这种并发执行机制原理运用在一个程序中,把一个程序划分为若干个子任务,多个子任务并发执行,每一个任务就是一个线程。这就是多线程程序。
优点
- 使用线程可以把占据时间长的程序中的任务放到后台去处理。
- 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
- 程序的运行速度可能加快。
- 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下可以释放一些珍贵的资源如内存占用等。
- 多线程技术在 iOS 软件开发中也有举足轻重的作用。
缺点
- 如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换。
- 更多的线程需要更多的内存空间。
- 线程可能会给程序带来更多“bug”,因此要小心使用。
- 线程的中止需要考虑其对程序运行的影响。
- 通常块模型数据是在多个线程间共享的,需要防止线程死锁情况的发生。
优势
多进程程序结构和多线程程序结构有很大的不同,多线程程序结构相对于多进程程序结构有以下的优势:
- 方便的通信和数据交换
线程间有方便的通信和数据交换机制。对于不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其他线程所用,这不仅快捷,而且方便。 2. 更高效地利用CPU 使用多线程可以加快应用程序的响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作置于一个新的线程,就可以避免这种尴尬的情况。同时,多线程使多 CPU 系统更加有效。操作系统会保证当线程数不大于 CPU 数目时,不同的线程运行于不同的 CPU 上。
概念区分
线程与多线程:线程是系统对代码的执行进程,如果将系统当做一个员工,被安排执行某个任务的时候,他不会对任何其他的任务作出响应。只有当这个任务执行完毕,才可以重新给他分配任务。一个程序都有一个主线程,负责执行程序必要的任务。 当我们处理一个消耗大的任务(如上传或下载图片),如果让主线程执行这个任务,它会等到动作完成,才继续后面的代码。在这段时间之内,主线程处于“忙碌”状态,也就是无法执行任何其他功能。体现在界面上就是,用户的界面完全“卡死”。 多线程是指,将原本线性执行的任务分开成若干个子任务同步执行,这样做的优点是防止线程“堵塞”,增强用户体验和程序的效率。缺点是代码的复杂程度会大大提高,而且对于硬件的要求也相应地提高。
应用
无论是过去还是现在,世界上大多数计算机仍然采用的是冯·诺依曼结构,这种结构的特点就是顺序处理,一个处理器在同个时刻只能处理一件事情。 Windows 95/NT 采用一种全新的任务调度策略,它把一个进程划分为多个线程,每个线程轮流占用 CPU 的运算时间,操作系统不断地把线程挂起、唤醒、再挂起、再唤程,如此反复,由于现在 CPU 的速度比较快,给人的感觉是多个线程在同时执行,就好像有多个 CPU 存在于计算机中一样。
并发与并行
并发:在同一个时间段内,两个或多个程序执行,有时间上的重叠(宏观上是同时,微观上仍是顺序执行)。在操作系统中,并发是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。 并行:在操作系统中是指,一组程序按独立异步的速度执行,不等于时间上的重叠(同一个时刻发生)。
并发与并行的区别
并发当有多个线程在操作时,如果系统只有一个 CPU,则它根本不可能真正同时进行一个以上的线程,它只能把 CPU 运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。这种方式我们称之为并发(Concurrent
)。
并行:当系统有一个以上 CPU 时,则线程的操作有可能非并发。当一个 CPU 执行一个线程时,另一个 CPU 可以执行另一个线程,两个线程互不抢占 CPU 资源,可以同时进行,这种方式我们称之为并行(Parallel
)。
区别:并发和并行是即相似又有区别的两个概念,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可以同时执行。
队列机制
队列是一种数据结构,它具有先进先出的特点,是一种应用很广泛的结构。在计算机或计算机之间,为了提高计算机或计算机之间的工作效率,我们经常采用队列机制。队列机制简单来说是基于队列,利用某种方案来提高工作效率。例如操作系统中作业、进程和线程基于队列机制调度。
队列与线程的关系
任务:每次执行的一段代码,比如下载一张图片,触发一个网络请求。 队列:队列是用来组织任务的,一个队列包含多个任务。 队列是对任务的描述,它可以包含多个任务,这是应用层的一种描述。线程是系统级的调度单位,它是更底层的描述。一个队列(并行队列)的多个任务可能会被分配到多个线程执行。 在 iOS 中主线程是一个线程,主队列是指主线程上的任务组织形式。 主队列只会在主线程执行,但主线程上执行的不一定就是主队列,还有可能是别的同步队列。同步操作不会开辟新的线程,所以当你自定义一个同步的串行或者并行队列时都是还在主线程执行。
同步与异步
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态。 异步的另外一种含义是计算机多线程的异步处理。与同步处理相对,异步处理不用阻塞当前线程来等待处理完成,而是允许后续操作,直至其它线程将处理完成,并回调通知此线程。
同步 sync:只能在当前线程按先后顺序依次执行任务,不具备开启新线程的能力。(阻塞当前线程,等待任务执行完成) 异步 async:在新的线程中执行任务,具备开启新线程的能力。(不阻塞当前线程,不等待任务执行完成)
看到这里,我们一定要清楚一点,我们日常开发中说的同步和异步是指的线程。串行、并行和并发指的是队列。不要把它们搞混淆了。
参考链接
参考链接:🔗
作者:鳄鱼不怕_牙医不怕
链接:https://juejin.im/post/6894166184687009799