什么是线程
线程是进程中的一条执行流,在linux下是通过pcb实现的程序调度
- linux下的pcb实际上是一个线程,并且这些pcb共用同一个虚拟地址空间,共享进程大部分资源,相较于传统的pcb更加轻量化一点,因此也被称之为轻量级进程(线程)
- linux下的进程其实是一个线程组,一个进程中可以有多个线程(多个pcb),线程是进程中的一条执行流
多进程/多线程的优缺点分析(多任务处理)
多线程的优点:
- 线程间通信更加方便灵活(全局变量,函数传参 - 公用同一个虚拟地址空间,只要知道地址就能访问同一块空间)
- 线程的创建/销毁成本更低(创建线程创建一个pcb,共用的数据只需要一个指针指向同一处就可以了)
- 同一个进程中线程间的调度成本更低(调度切换不需要切换页表…)
多进程的优点:
- 具有独立性,因此更加的稳定,健壮(异常和某些系统调用exit针对的是整个进程生效)
对主功能程序安全性稳定性要求更高的最好使用多进程(shell/服务器),剩下的多线程。
如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现!
线程异常:
- 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
- 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止, 该进程内的所有线程也就随即退出
cpu上下文切换详解
上下文切换(有时也称做进程切换或任务切换)是指 cpu 从一个进程或线程切换到另一个进程或线程。
进程(有时候也称做任务)是指一个程序运行的实例。在 linux 系统中,线程就是能并行运行并且与他们的父进程(创建他们的进程)共享同一地址空间(一段内存区域)和其他资源的轻量级的进程。
上下文是指某一时间点 cpu 寄存器和程序计数器 的内容。
- 寄存器是 cpu 内部的数量较少但是速度很快的内存(与之对应的是 cpu 外部相对较慢的 ram 主内存)。寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速度。
- 程序计数器是一个专用的寄存器,用于表明指令序列中 cpu 正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置,具体依赖于特定的系统。
稍微详细描述一下,上下文切换可以认为是内核(操作系统的核心)在 cpu 上对于进程(包括线程)进行以下的活动:
- 挂起一个进程,将这个进程在 cpu 中的状态(上下文)存储于内存中的某处。
- 在内存中检索下一个进程的上下文并将其在 cpu 的寄存器中恢复。
- 计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程。
上下文切换有时被描述为内核挂起 cpu 当前执行的进程,然后继续执行之前挂起的众多进程中的某一个。尽管这么说对于澄清概念有所帮助,但是这句话本身可能有一点令人困惑。因为通过定义可以知道,进程是指一个程序运行的实例。所以说成挂起一个进程的运行可能更适合一些。
上下文切换与模式切换
上下文切换只能发生在内核态 中。
内核态是 cpu 的一种有特权的模式,在这种模式下只有内核运行并且可以访问所有内存和其他系统资源。其他的程序,如应用程序,在最开始都是运行在用户态,但是他们能通过系统调用来运行部分内核的代码。系统调用在类 unix 系统中是指活跃的进程(正在运行在 cpu 上的进程)对于内核所提供的服务的请求,例如输入/输出(i/o)和进程创建(创建一个新的进程)。i/o 可以被定义为任何信息流入或流出 cpu 与主内存(ram)。也就是说,一台电脑的 cpu和内存与该电脑的用户(通过键盘或鼠标)、存储设备(硬盘或磁盘驱动)还有其他电脑的任何交流都是 i/o。
这两种模式(用户态和内核态)在类 unix 系统中共存意味着当系统调用发生时 cpu 切换到内核态是必要的。这应该叫做模式切换而不是上下文切换,因为没有改变当前的进程。
上下文切换在多任务操作系统中是一个必须的特性。多任务操作系统是指多个进程运行在一个 cpu 中互不打扰,看起来像同时运行一样。这个并行的错觉是由于上下文在高速的切换(每秒几十上百次)。当某一进程自愿放弃它的 cpu 时间或者系统分配的时间片用完时,就会发生上下文切换。
上下文切换有时也因硬件中断而触发。硬件中断是指硬件设备(如键盘、鼠标、调试解调器、系统时钟)给内核发送的一个信号,该信号表示一个事件(如按键、鼠标移动、从网络连接接收到数据)发生了。
现在多数 cpu 都支持硬件上下文切换。然而,大多数现代的操作系统通过软件实现上下文切换,而非使用硬件上下文切换,这样能够运行在任何 cpu 上。同时,使用软件上下文切换可以尝试获得更好的性能。软件的上下文切换最先在 linux 2.4 中实现。软件上下文切换号称的一个主要优点是,硬件的机制保存了几乎所有 cpu 的状态,软件能够有选择性的保存需要被保存的部分并重新加载。然而这个行为对于提升上下文切换的性能到底有多重要,还有一点疑问。其拥护者还宣称,软件上下文切换有提高切换代码的可能性,它有助于提高正在加载的数据的有效性,从而进一步提高性能。
上下文切换的消耗
上下文切换通常是计算密集型的。
也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 cpu 时间,事实上,可能是操作系统中时间消耗最大的操作。
linux相比与其他操作系统(包括其他类 unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
cpu密集型 vs i/o密集型
- cpu密集型:
系统的硬盘、内存性能相对cpu要好很多。系统运作大部分的状况是cpu loading 100%,cpu要读/写i/o(硬盘/内存),i/o在很短的时间就可以完成,而cpu还有许多运算要处理,cpu loading很高。
- i/o密集型:
系统的cpu性能相对硬盘、内存要好很多。此时,系统运作,大部分的状况是cpu在等i/o (硬盘/内存) 的读/写操作,此时cpu loading并不高。
-
cpu密集型程序:程序中进行大量的数据运算处理(在cpu资源足够的情况下,就可以同时处理,提高效率)
-
io密集型程序:多任务并行处理(单磁盘可以并行压缩io等待事件/多磁盘可以实现同时处理)
程序中大量进行io操作,对cpu要求并不高因此执行流个数没有太大要求
注意:
-
并非线程越多越好,调度的时间成本会提高(创建线程很多,而cpu资源不够多,会造成大量的进程切换调度成本提高)
-
cpu 密集型程序创建多少个线程合适?
最佳线程数 = cpu核心数 1: 1是为了预防有个线程被阻塞,cpu可以调用其他线程。
- i/o密集型程序创建多少个线程合适?
最佳线程数 = (1/cpu利用率) = 1 (i/o耗时/cpu耗时);(一般为2*cpu核心数)
进程和线程、协程
- 进程是操作系统资源分配的基本单位(操作系统分配资源给一个运行中的程序,而这个运行中的程序有可能有多个pcb)
- 线程是cpu执行和调度的基本单位(线程本身不拥有资源,资源来自于它的进程)
- 协程是一种用户态的轻量级线程,协程的调度完全由用户控制。从技术的角度来说,“协程就是你可以暂停执行的函数”。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
进程与线程进行比较:
- 进程是资源分配单位,线程是cpu调度单位。
- 进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈。
- 线程同样具有就绪、阻塞和运行三种基本状态,同样具有状态之间的转换关系。
- 线程能减少并发执行的时间和空间开销。
协程与线程进行比较:
-
一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核cpu。
-
线程进程都是同步机制,而协程则是异步
-
协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
线程间的独有与共享
独有: 栈,寄存器,信号屏蔽字,errno,标识符,调度优先级
共享: 虚拟地址空间(代码段,数据段),文件描述符表,信号处理方式,工作路径,用户id和组id
- 为什么信号是先注销,再处理?
信号是针对整个进程通知事件进行处理的,但是一个信号只需要被处理一次就够了;然而一个进程有可能会有多个执行流,到底谁处理这个事件(谁拿到时间片,谁能处理谁处理),有的线程不希望操作被信号打断,就可以独立屏蔽这个信号(信号屏蔽字)
-
多线程,多个pcb作为独立执行流肯定是要同时运行,如何做到不会调用栈混乱(每个pcb都独有自己的栈)(栈)
-
errno是一个全局变量,保存系统调用使用完成后的错误编号,多个线程同时执行,都会使用系统调用,都会修改errno,若errno共用,就会存在二义问题(errno)
多线程多进程的区别
维度 | 多进程 | 多线程 | 总结 |
---|---|---|---|
数据共享、同步 | 数据是分开的:共享复杂,需要用ipc;同步简单 | 多线程共享进程数据:共享简单;同步复杂 | 各有优势 |
内存、cpu | 占用内存多,切换复杂,cpu利用率低 | 占用内存少,切换简单,cpu利用率高 | 线程占优 |
创建销毁、切换 | 创建销毁、切换复杂,速度慢 | 创建销毁、切换简单,速度快 | 线程占优 |
编程调试 | 编程简单,调试简单 | 编程复杂,调试复杂 | 进程占优 |
可靠性 | 进程间不会相互影响 | 一个线程挂掉将导致整个进程挂掉 | 进程占优 |
分布式 | 适应于多核、多机分布 ;如果一台机器不够,扩展到多台机器比较简单 | 适应于多核分布 | 进程占优 |
进程和线程的关系如下图:
并发和并行的区别
并发(concurrency)和并行(parallellism)是:
- 解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
- 解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
- 解释三:并行是在多台处理器上同时处理多个任务。如 hadoop 分布式集群,并发是在一台处理器上“同时”处理多个任务。
所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。
并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。
并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并行的假象,并行要求程序能够同时执行多个操作,而并发只是要求程序假装同时执行多个操作(每个小时间片执行一个操作,多个操作快速切换执行)。
当有多个线程在操作时,如果系统只有一个 cpu,则它根本不可能真正同时进行一个以上的线程,它只能把 cpu 运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态.这种方式我们称之为并发(concurrent)。
当系统有一个以上 cpu 时,则线程的操作有可能非并发。当一个 cpu 执行一个线程时,另一个 cpu 可以执行另一个线程,两个线程互不抢占 cpu 资源,可以同时进行,这种方式我们称之为并行(parallel)。
知识点习题
- 下面有关内核线程和用户线程说法错误的是?
a. 用户线程因 i/o 而处于等待状态时,整个进程就会被调度程序切换为等待状态,其他线程得不到运行的机会
b. 内核线程只运行在内核态,不受用户态上下文的影响。
c. 用户线程和内核线程的调度都需要经过内核态。
d. 内核线程有利于发挥多处理器的并发优势,但却占用了更多的系统开支。
正确答案: c
答案解析:
用户级线程的创建、撤消和调度不需要os内核的支持,是在语言(如c )这一级处理的;而内核支持线程的创建、撤消和调度都需os内核提供支持,而且与进程的创建、撤消和调度大体是相同的。
- 关于多线程和多进程编程,下面描述正确的是():
a. 多进程里,子进程可获得父进程的所有堆和栈的数据;而线程会与同进程的其他线程共享数据,拥有自己的栈空间
b. 线程因为有自己的独立栈空间且共享数据,所以执行的开销相对较大,同时不利于资源管理和保护
c. 线程的通信速度更快,切换更快,因为他们在同一地址空间内
d. 线程使用公共变量/内存时需要使用同步机制,因为他们在同一地址空间内
e. 因多线程里,每个子进程有自己的地址空间,因此相互之间通信时,线程不如进程灵活和方便
f. 进程比线程更健壮,但是进程比线程更容易杀掉
正确答案: a c d
答案解析:
线程和进程的区别联系:
- 进程:子进程是父进程的复制品。子进程获得父进程数据空间、堆和栈的复制品。
- 线程:相对与进程而言,线程是一个更加接近与执行体的概念,它可以与同进程的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
两者都可以提高程序的并发度,提高程序运行效率和响应时间。
线程和进程在使用上各有优缺点:
- 线程执行开销小,但不利于资源管理和保护;而进程正相反。
- 线程适合于在smp机器上运行,而进程则可以跨机器迁移。
根本区别就一点:用多进程每个进程有自己的地址空间(address space),线程则共享地址空间。所有其它区别都是由此而来的:
1、速度:线程产生的速度快,线程间的通讯快、切换快等,因为他们在同一个地址空间内。
2、资源利用率:线程的资源利用率比较好也是因为他们在同一个地址空间内。
3、同步问题:线程使用公共变量/内存时需要使用同步机制还是因为他们在同一个地址空间内
进程比线程更健壮,但是因为进程需要释放的资源更多进程比线程更难杀掉
- 多线程中栈与堆是公有的还是私有的()
a. 栈公有,堆私有
b. 栈公有,堆公有
c. 栈私有,堆公有
d. 栈私有,堆私有
正确答案: c
答案解析:
堆在一起的东西,肯定是公用(公有)的,你占(栈)有的东西,肯定是你自己私有的。
在多线程环境下,每个线程拥有一个栈和一个程序计数器。栈和程序计数器用来保存线程的执行历史和线程的执行状态,是线程私有的资源。其他的资源(比如堆、地址空间、全局变量)是由同一个进程内的多个线程共享
- 对于线程和进程的描述,正确的是?
a. 进程退出前,需要手动销毁所有线程。否则进程退出后,依然会有线程在运行,可能会导致不可预测的错误。
b.在单核的cpu上使用多线程,并不会比单线程有优势
c.进程是资源分配的基本单位,线程是执行和调度的基本单位
d.多线程使用全局变量时,需要使用同步机制,因为他们在同一地址空间内
正确答案: b,c,d
答案解析:
进程退出,进程中的所有线程就会随之自动退出的
- 两个线程都会调用函数f,可能出现的结果是?
int g = 1; // g 为全局变量
void f(){
g;
printf("%d ", g);
}
a. 2 3
b. 3 3
c. 3 2
d. 2 2
正确答案: a,b,c
答案解析:
a,b,d 就不用解释了,很容易理解
c:自己执行 ,还没来得及打印,2只是写入缓冲区了(没有使用\n刷新缓冲区),下一个线程 了,输出就变成3,cpu轮转回来,将缓冲区中的2写入标准输出,打印2
- 在单路cpu的计算机中,在下面关于并发性的叙述中正确的是()。
a. 并发性是指若干事件在同一时刻发生
b. 并发性是指若干事件在不同时刻发生
c. 并发性是指若干事件在同一时间间隔发生
d. 并发性是指若干事件在不同时间间隔发生
正确答案: c
答案解析:
并发性是指两个或多个事件在同一时间间隔内发生。
并行性是指两个或多个事件在同一时刻发生
- 在intel cpu上,以下多线程对int型变量x的操作,哪几个不是原子操作,假定变量的地址都是对齐的。( )
a. x=y
b. x
c. x
d. x=1
正确答案: d
答案解析:
前三个都至少需要先读取,再操作,非原子操作。而d的话,直接赋值。
- 下面关于用户级线程和内核级线程的描述,错误的是?
a. 用户级线程可以在不支持内核级线程的操作系统上实现
b. 用户级线程间的切换比内核级线程间的切换效率高
c. 内核级线程是操作系统调度器管理和调度
d. 内核级线程,线程的创建、撤销和切换等,都需要内核直接实现
e. 操作系统为每个用户级线程建立一个线程控制块
正确答案: e
答案解析:
- 内核级线程:
(1)线程的创建、撤销和切换等,都需要内核直接实现,即内核了解每一个作为可调度实体的线程。
(2)这些线程可以在全系统内进行资源的竞争。
(3)内核空间内为每一个内核支持线程设置了一个线程控制块(tcb),内核根据该控制块,感知线程的存在,并进行控制。
在一定程度上类似于进程,只是创建、调度的开销要比进程小。有的统计是1:10
- 用户级线程:
(1)用户级线程仅存在于用户空间。——>对比内核(3)
(2)内核并不能看到用户线程。——>重要的区别
(3)内核资源的分配仍然是按照进程进行分配的;各个用户线程只能在进程内进行资源竞争。
内核级线程是操作系统内核实现、管理和调度的一种线程。
以下是用户级线程和内核级线程的区别:
(1)内核支持线程是os内核可感知的,而用户级线程是os内核不可感知的。
(2)用户级线程的创建、撤消和调度不需要os内核的支持,是在语言(如java)这一级处理的;而内核支持线程的创建、撤消和调度都需os内核提供支持,而且与进程的创建、撤消和调度大体是相同的。
(3)用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。
(4)在只有用户级线程的系统内,cpu调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,cpu调度则以线程为单位,由os的线程调度程序负责线程的调度。
(5)用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。
内核线程的优点:
(1)当有多个处理机时,一个进程的多个线程可以同时执行。
缺点:
(1)由内核进行调度。
用户进程的优点:
(1)线程的调度不需要内核直接参与,控制简单。
(2) 可以在不支持线程的操作系统中实现。
(3)创建和销毁线程、线程切换代价等线程管理的代价比内核线程少得多。
(4)允许每个进程定制自己的调度算法,线程管理比较灵活。这就是必须自己写管理程序,与内核线程的区别
(5)线程能够利用的表空间和堆栈空间比内核级线程多。
(6)同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起。另外,页面失效也会产生同样的问题。
缺点:
(1)资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下分时复用