文章目录
- 1.进程/线程/协程
- 1.1辨别进程和线程的异同
- 1.2优缺点
- 1.2.1进程
- 1.2.2线程
- 1.3进程/线程之间通信的方法
- 1.3.1进程之间通信的方法
- 1.3.2线程之间通信的方法
- 1.4什么是线程上下文切换
- 1.5协程
- 1.5.1协程的定义?
- 1.5.2使用协程的原因?
- 1.5.3协程的优缺点
- (1)优点
- (2)缺点
- 1.6什么是线程死锁?如何避免?
- 1.7杀死进程的方法
- 1.7.1 linux
- 1.7.2 windows
- 2.线程的生命周期
- 2.1线程的六种生命状态
- 2.2辨别sleep()方法和wait()方法的异同?
- 2.3notify()和notifyAll的区别
- 2.4为什么wait()要包在同步块中?
- 2.5如何停止一个线程的运行?
- 3.软连接和硬连接有什么区别?
- 4.内核态和用户态
- 5.有哪些进程调度算法
- 5.1先来先服务调度算法
- 5.2最短作业优先调度算法
- 5.3高响应比优先调度算法
- 5.4时间片轮转调度算法
1.进程/线程/协程
1.1辨别进程和线程的异同
进程和线程之间的区别可以从本质区别、开销方面、稳定性方面、内存分配方面、包含关系共五个角度去分析:
- 本质区别:
- 进程是操作系统资源分配的基本单位,是程序的一次执行,每个进程都有自己的地址空间、内存、数据栈及其他辅助记录运行轨迹的数据
- 线程是任务调度和执行的基本单位,是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位
- 开销方面:
- 每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;
- 线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小
- 稳定性方面:
- 进程中某个线程如果崩溃了,可能会导致整个进程都崩溃
- 进程中的子进程崩溃,并不会影响其他进程。
- 内存分配方面:
- 系统在运行的时候会为每个进程分配不同的内存空间
- 而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源
- 包含关系:
- 没有线程的进程可以看做是单线程的
- 如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;
- 线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程
1.2优缺点
1.2.1进程
- 优点:
- 顺序程序的特点:具有封闭性和可再现性;
- 程序的并发执行和资源共享。多道程序设计出现后,实现了程序的并发执行和资源共享,提高了系统的效率和系统的资源利用率。
- 缺点:
- 操作系统调度切换多个线程要比切换调度进程在速度上快的多。
- 而且进程间内存无法共享,通讯也比较麻烦。
1.2.2线程
- 优点:
- 线程之间切换速度快
- 线程间内存共享,资源共享
- 缺点:
- 调度时, 要保存线程状态,频繁调度, 需要占用大量的机时;
- 程序设计上容易出错(线程同步问题)
1.3进程/线程之间通信的方法
1.3.1进程之间通信的方法
- 由于每个进程的用户空间都是独立的,不能相互访问,这时就需要借助内核空间来实现进程间通信,原因很简单,每个进程都是共享一个内核空间。
- Linux 内核提供了不少进程间通信的方式,其中最简单的方式就是管道,管道分为「匿名管道」和「命名管道」。
- 匿名管道:顾名思义,它没有名字标识,匿名管道是特殊文件只存在于内存,没有存在于文件系统中,shell 命令中的「|」竖线就是匿名管道,通信的数据是无格式的流并且大小受限,通信的方式是单向的,数据只能在一个方向上流动,如果要双向通信,需要创建两个管道,匿名管道是只能 用于存在父子关系的进程间通信,匿名管道的生命周期随着进程创建而建立,随着进程终止而消失。
- 命名管道:突破了匿名管道只能在亲缘关系进程间的通信限制,因为使用命名管道的前提,需要在文件系统创建一个类型为 p 的设备文件,那么毫无关系的进程就可以通过这个设备文件进行通信。另外,不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。
- 消息队列:克服了管道通信的数据是无格式的字节流的问题,消息队列实际上是保存在内核的「消息链表」,消息队列的消息体是可以用户自定义的数据类型,发送数据时,会被分成一个一个独立的消息体,当然接收数据时,也要与发送方发送的消息体的数据类型保持一致,这样才能保证读取的数据是正确的。消息队列通信的速度不是最及时的,毕竟每次数据的写入和读取都需要经过用户态与内核态之间的拷贝过程。
- 共享内存:以解决消息队列通信中用户态与内核态之间数据拷贝过程带来的开销,它直接分配一个共享空间,每个进程都可以直接访问,就像访问进程自己的空间一样快捷方便,不需要陷入内核态或者系统调用,大大提高了通信的速度,享有最快的进程间通信方式之名。但是便捷高效的共享内存通信,带来新的问题,多进程竞争同个共享资源会造成数据的错乱。
- 需要 信号量来保护共享资源,以确保任何时刻只能有一个进程访问共享资源,这种方式就是互斥访问。信号量不仅可以实现访问的互斥性,还可以实现进程间的同步,信号量其实是一个计数器,表示的是资源个数,其值可以通过两个原子操作来控制,分别是 P 操作和 V 操作。
1.3.2线程之间通信的方法
在Linux系统中,线程间通信的方式包括:
- 互斥锁(Mutex):线程可以使用互斥锁来保护共享资源,确保同时只有一个线程可以访问该资源。
- 条件变量:线程可以使用条件变量来等待特定条件的发生,以实现线程间的协调和通知。
- 信号量:线程可以使用信号量来控制对共享资源的访问,实现线程间的同步和互斥。
- 读写锁:允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。
1.4什么是线程上下文切换
线程在执行过程中会有自己的运行条件和状态(也称上下文),比如程序计数器,虚拟机栈和本地方法栈等。
当出现如下情况的时候,会发生线程切换。
- 主动让出 CPU,比如调用了
sleep()
,wait()
等。 - 时间片用完,因为操作系统要防止一个线程或者进程长时间占用 CPU 导致其他线程或者进程饿死。
- 调用了阻塞类型的系统中断,比如请求 IO,线程被阻塞。
线程切换意味着需要保存当前线程的上下文(比如程序计数器,虚拟机栈和本地方法栈),留待线程下次占用 CPU 的时候恢复现场。并加载下一个将要占用 CPU 的线程上下文。这就是所谓的 上下文切换。频繁上下文切换就会造成整体效率低下。
1.5协程
1.5.1协程的定义?
- 协程, 我们又称为微线程,协程它不像线程和进程那样,需要进行系统内核上的上下文切换,协程的上下文切换是由开发人员决定的。
- 协程是一种用户级的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
1.5.2使用协程的原因?
- 与线程相关的概念就是抢占式多任务(Preemptive multitasking),而与协程相关的是协作式多任务
- 不管是进程还是线程,每次阻塞、切换都需要陷入系统调用(system call),先让CPU跑操作系统的调度程序,然后再由调度程序决定该跑哪一个进程(线程)。由于抢占式调度执行顺序无法确定的特点,使用线程时需要非常小心地处理同步问题,而协程完全不存在这个问题(事件驱动和异步程序也有同样的优点)。
- 因为协程是用户自己来编写调度逻辑的,对于我们的CPU来说,协程其实是单线程,所以CPU不用去考虑怎么调度、切换上下文,这就省去了CPU的切换开销,所以协程在一定程度上又好于多线程。
1.5.3协程的优缺点
(1)优点
- 无需系统内核的上下文切换,减小开销;
- 无需原子操作锁定及同步的开销,不用担心资源共享的问题;
- 单线程即可实现高并发,单核 CPU 即便支持上万的协程都不是问题,所以很适合用于高并发处理,尤其是在应用在网络爬虫中。
(2)缺点
- 无法使用 CPU 的多核
- 处处都要使用非阻塞代码
1.6什么是线程死锁?如何避免?
-
死锁(Deadlock)描述的是这样一种情况:多个进程/线程同时被阻塞,它们中的 一个或者全部都在等待 某个资源被释放 。由于进程/线程被无限期地阻塞,因此程序不可能正常终止。
-
四个必要条件
- 互斥(x):资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
- 请求并保持:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
- 非抢占:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放
- 循环等待(资源有序分配,即标序号):在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。
-
破坏死锁:那么避免死锁问题就只需要破环其中一个条件就可以,最常见的并且可行的就是**使用资源有序分配法,来破环环路等待条件**。
- 例如:进程PA,使用资源的顺序是R1,R2;
进程PB,使用资源的顺序是R2,R1;
若采用动态分配有可能形成环路条件,造成死锁。
采用有序资源分配法:R1的编号为1,R2的编号为2;
PA:申请次序应是:R1,R2
PB:申请次序应是:R1,R2
- 例如:进程PA,使用资源的顺序是R1,R2;
1.7杀死进程的方法
1.7.1 linux
首先,使用ps命令查找进程的PID(进程ID),然后使用kill命令加上PID来终止进程。例如:
ps -ef | grep <进程名> // 查找进程的PID
kill <PID> // 终止进程
1.7.2 windows
netstat -ano | findstr "1098" //查询端口号对应的进程id
taskkill /T /F /PID //强制(/F参数)杀死 PID 为 xxx 的所有进程,包括子进程(/T参数)
2.线程的生命周期
2.1线程的六种生命状态
- 如图,线程有六种生命状态:新建、可执行(就绪、运行)、阻塞、等待、计时等待、死亡
2.2辨别sleep()方法和wait()方法的异同?
- 相同点:两者都可以暂停线程的执行
- 不同点:
- 是否有释放锁:
- sleep()方法没有释放锁(synchronized的锁),用于暂停执行任务
- wait() 方法释放了锁,用于线程之间的交互
- 是否自动苏醒:
- sleep()方法执行完成后,线程会自动苏醒,或者也可以使用 wait(long timeout) 超时后线程会自动苏醒。
- wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的
notify()
或者notifyAll()
方法。
- 归属类不同:
- sleep()是 Thread 类的静态本地方法 (sleep()方法是让当前线程暂停执行,不涉及对象锁)
- wait() 则是 Object类的 本地方法,wait()是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁)。
- 是否有释放锁:
2.3notify()和notifyAll的区别
同样是唤醒等待的线程,同样最多只有一个线程能获得锁,同样不能控制哪个线程获得锁。
区别在于:
- notify:
- 唤醒一个线程,其他线程依然处于wait的等待唤醒状态
- 如果被唤醒的线程结束时没调用notify,其他线程就永远没人去唤醒,只能等待超时,或者被中断
- notifyAll:
- 唤醒全部线程,所有线程退出wait的状态,开始竞争锁
- 但只有一个线程能抢到,这个线程执行完后,其他线程又会有一个幸运儿脱颖而出得到锁
2.4为什么wait()要包在同步块中?
- 线程安全:在同步块中调用
wait()
方法可以确保线程在调用wait()
前已经获取了对象的锁,避免多线程之间的竞争和数据不一致性问题。 - 对象监视器:
wait()
方法会释放对象的监视器(monitor),其他线程可以获取该对象的监视器并执行同步操作,确保线程之间的协作和同步。 - 唤醒机制:当调用
wait()
方法后,线程会进入等待状态,只有在其他线程调用notify()
或notifyAll()
方法唤醒该线程时,线程才会继续执行。在同步块中调用wait()
可以保证线程被正确唤醒。
2.5如何停止一个线程的运行?
主要有这些方法:
- 异常法停止:线程调用interrupt()方法后,在线程的run方法中判断当前对象的interrupted()状态,如果是中断状态则抛出异常,达到中断线程的效果。
- 在沉睡中停止:先将线程sleep,然后调用interrupt标记中断状态,interrupt会将阻塞状态的线程中断。会抛出中断异常,达到停止线程的效果
- stop()暴力停止:线程调用stop()方法会被暴力停止,方法已弃用,该方法会有不好的后果:强制让线程停止有可能使一些请理性的工作得不到完成。
- 使用return停止线程:调用interrupt标记为中断状态后,在run方法中判断当前线程状态,如果为中断状态则return,能达到停止线程的效果。
3.软连接和硬连接有什么区别?
-
软连接:实际上是一个指向目标文件的路径的符号链接,类似于Windows系统中的快捷方式,创建软连接不会占用目标文件的inode节点,只是简单地指向目标文件的路径。删除原始文件后,软连接仍然存在,但指向的目标文件失效,称为"悬空链接"。软链接可以跨文件系统创建软连接。
-
硬连接:是指多个文件实际上指向同一个inode节点,即多个文件共享同一块数据块。创建硬连接会增加目标文件的链接计数,删除任何一个硬连接并不会影响其他硬连接指向的文件数据。只能在同一文件系统内创建硬连接。
4.内核态和用户态
内核态和用户态是操作系统中的两种运行模式。它们的主要区别在于权限和可执行的操作:
- 内核态(Kernel Mode):在内核态下,CPU可以执行所有的指令和访问所有的硬件资源。这种模式下的操作具有更高的权限,主要用于操作系统内核的运行。
- 用户态(User Mode):在用户态下,CPU只能执行部分指令集,无法直接访问硬件资源。这种模式下的操作权限较低,主要用于运行用户程序。
内核态的底层操作主要包括:内存管理、进程管理、设备驱动程序控制、系统调用等。这些操作涉及到操作系统的核心功能,需要较高的权限来执行。
分为内核态和用户态的原因主要有以下几点:
- 安全性:通过对权限的划分,用户程序无法直接访问硬件资源,从而避免了恶意程序对系统资源的破坏。
- 稳定性:用户态程序出现问题时,不会影响到整个系统,避免了程序故障导致系统崩溃的风险。
- 隔离性:内核态和用户态的划分使得操作系统内核与用户程序之间有了明确的边界,有利于系统的模块化和维护。
内核态和用户态的划分有助于保证操作系统的安全性、稳定性和易维护性。
5.有哪些进程调度算法
5.1先来先服务调度算法
顾名思义,先来后到,每次从就绪队列选择最先进入队列的进程,然后一直运行,直到进程退出或被阻塞,才会继续从队列中选择第一个进程接着运行。
5.2最短作业优先调度算法
顾名思义,它会 优先选择运行时间最短的进程来运行,这有助于提高系统的吞吐量。
5.3高响应比优先调度算法
每次进行进程调度时,先计算「响应比优先级」,然后把「响应比优先级」最高的进程投入运行
响应比优先级」的计算公式:
5.4时间片轮转调度算法
每个进程被分配一个时间段,称为时间片(*Quantum*),即允许该进程在该时间段中运行。
- 如果时间片用完,进程还在运行,那么将会把此进程从 CPU 释放出来,并把 CPU 分配给另外一个进程;
- 如果该进程在时间片结束前阻塞或结束,则 CPU 立即进行切换;