进程管理
进程与线程
1、进程和线程的区别?
进程 | 线程 | |
---|---|---|
定义 | 进程是操作系统分配资源的单位 | 线程是任务调度和执行的基本单位 |
切换情况 | 进程CPU环境(栈、寄存器、页表和文件句柄等)的保存以及新调度进程环境的设置 | 保存和设置程序计数器,少量寄存器和栈的内容 |
关联特性 | 不同进程之间实现并发,各自占用CPU时间片 | 一个进程内部多个线程并发执行 |
系统开销 | 切换虚拟地址空间,切换内核栈和硬件上下文,CPU告诉缓存失效、页表切换,开销很大 | 切换时只需要保存和设置少量寄存器内容,有一定的开销 |
通信方式 | 进程之间的通信需要借助操作系统,比如管道、消息队列、Socket通信等 | 线程之间可以直接读写进程数据段,来进行通信 |
安全性 | 多进程安全性比较好,在某一个进程出问题时,其他进程一般不会受影响 | 在多线程的情况下,一个线程执行了非法操作会导致整个进程退出 |
- **定义区别:**创建进程的时候需要分配空间,比如栈区、文件映射区、堆区、静态区、常量区、代码段,因此也会说进程是资源分配的最小单位;线程则是程序执行的基本单位,每个进程中都有唯一的主线程,主线程和进程是相互依存的关系。
- **共享资源区别:**每个进程有自己的独立地址空间,不与其他进程分享;一个进程可以有多个线程,彼此共享一个地址空间。堆内存、文件、套接字等资源都归进程管理,同一个进程里的多个线程可以共享使用。每个进程占用的内存和资源,会在进程退出或被杀死时返回给操作系统。
- **上下文切换区别:**进程和线程上下文切换的时候,都会发生内核态和用户态的切换,进程间的切换需要切换CPU上下文、切换页表,切换页表会影响TLB(对页表的高速缓存)的命中率,那么虚拟地址转换为实际物理地址就会变慢,但是线程只需要切换CPU上下文,不会改变虚拟地址空间,不会影响TLB命中率。
- **安全性区别:**并发应用可以用多进程或多线程的方式。多线程由于可以共享资源,效率高;多进程不共享地址空间和资源,在需要共享数据时效率低。但多进程性能好,在某一个进程出问题时,其他进程一般不受影响;而在多线程的情况下,一个线程执行了非法操作会导致整个进程退出。
2、进程有哪些状态?
理论上,进程分为5种状态,创建状态、就绪状态、运行状态、阻塞状态、结束状态。
一个进程刚开始创建的时候是创建状态,随后会进入就绪状态,等待被操作系统调度,当进程被调度器选中后,就会进入运行状态,此时进程就持有CPU执行权,当进程发生I/O事件的时候,就会进入阻塞状态,等待I/O事件完成,I/O事件完成后进程就恢复为就绪状态,当进程退出后,就会变为结束状态。
Linux进程中有7中状态:
- 可运行状态,该状态表示进程正在运行或者等待被调度。
- 在运行中的进程,一旦要进行一些I/O操作,需要等待I/O完毕,这个时候会释放CPU,进入睡眠状态。Linux将睡眠状态分为了两种,一种是可中断的睡眠状态,另一种是不可中断的睡眠状态,这两种状态的区别是能否响应收到的信号,可中断的睡眠状态在等待I/O完毕期间,如果收到了其他信号,还是可以被唤醒,而不可中断的睡眠状态在等待I/O完毕期间,只有等待I/O完毕才有可能返回运行状态,任何信号都无法打断它。如果这种状态的进程出错,无法杀死,只有重启。
- 暂停状态,当进程接收到SIGSTOP、SIGTTIN、SIGTSTP、SIGTTOU信号之后进入该状态。
- 跟踪状态,当对进程进行gdb调试的时候,进程会进入跟踪状态。
- 僵尸状态,当子进程先于父进程退出后,父进程没有执行wait或waitpid函数来收回子进程的时候,子进程的状态就会变为僵尸状态,表示已经死亡的进程,但是进程ID还留着。
- 结束状态,当进程正确退出后,就进入结束状态,是进程的最终状态。
3、僵尸进程,孤儿进程,守护进程的区别?
僵尸进程是指子进程已经终止,但其父进程尚未调用wait()或waitpid()函数来获取子进程的终止状态,导致子进程的进程描述符仍然保留在系统进程表中,成为僵尸进程,僵尸进程不占用系统资源,但会占用一个进程PID。
孤儿进程是指父进程先于子进程退出或异常终止,导致子进程成为孤儿进程。孤儿进程会被init进程(进程ID为1)接管,init进程会成为孤儿进程的新的父进程。
守护进程是在后台运行的一种特殊机制,不与任何终端关联,关闭终端并不会影响守护进程的生命周期,守护进程的生命周期通常是伴随系统的启动和关闭,会一直在后台运行。
协程
1、什么是协程?
协程是一个用户态的线程,用户在堆上模拟出协程的栈空间。当需要进行协程上下文切换的时候,主线程只需要交换栈空间和恢复协程的一些相关的寄存器状态,就可以实现上下文切换。相比于线程上下文切换,没有了从用户态转换到内核态的切换成本。
2、协程和线程有什么区别?
- 线程调度由内核负责,协程调度由用户负责,所以协程的切换是比线程快的,因为协程的切换不用经过操作系统用户态与内核态的切换,并且协程的切换只需要保留极少的状态和寄存器变量值,比如SP、BP、PC寄存器,而线程的切换会保留额外的寄存器变量值,比如浮点寄存器。
- 线程的栈比协程栈大很多,线程的栈大小一般在创建时指定,默认8MB,如果创建成千上万个线程,对于系统会有很大负担,而协程通常是KB级别,比如2K,所以即使创建成千上万个协程,对于系统来说负担也不是太大。另外,线程的栈在运行时不能更改,而协程在Go运行时的帮助下可以动态检测栈的大小,进行扩容和收缩。
- 协程的调度是协作式调度,当一个协程处理完自己的任务之后,可以主动将执行权限让给其他协程,而线程是抢占式调度,线程的切换的主动权不再线程自己身上。
进程间通信
1、进程间有哪些通信方式?
进程间通信的方式主要有管道、消息队列、共享内存、信号、信号量和Socket通信。
- 管道通信的数据是无格式的字节流,并且通信方向是单向的,只能在一个方向上流动,管道分为匿名管道和有名管道,匿名管道是没有文件实体的,只能用于存在父子关系的进中程间通信,匿名管道的生命周期随着进程创建而建立,随着进程终止终止而消失。而有名的管道可以用于任何进程间的通信,并且数据可以持久化保存,即使创建的进程退出,其他进程仍然可以使用该管道。
- 消息队列在内核中是通过链表来组织消息的,克服了管道通信的数据是无格式的问题。
- 管道和消息队列在读写数据的时候,都需要进过用户态和内核态之间的拷贝过程,共享内存就解决了这个问题,多个进程可以将共享内存映射到各自的虚拟地址空间,实现共享数据的读写,不会涉及用户态和内核态之间的数据拷贝,所以共享内存方式是进程间通信方式里最高效的,不过共享内存带来新的问题,多进程竞争同个共享资源会造成数据的错乱,因为需要同步机制来保证多进程下的读写数据安全。
- 信号量可以实现进程间的同步和互斥访问共享资源,信号量其实是一个计数器,表示的是资源个数,当一个进程想要访问资源时,他会尝试递减信号量(称为P操作)。如果信号量的值大于0,这个操作会成功,否则进程阻塞,知道信号量变为正数。当进程完成对资源的访问后,它会递增信号量(称为V操作),允许其他阻塞的进程访问资源。
- 信号是一种异步的通知机制,用于通知进程某个事件已经发生。当一个信号发送给一个进程时,操作系统会中断进程的正常流程来处理信号,只适用于简单的通知和事件处理。
- 跨主机通信,通过Socket,可以实现TCP或UDP协议的通信方式。
2、有名管道和匿名管道的区别?
持久性、进程间关系。
- 匿名管道没有文件实体,不能用于非亲缘进程间通信,只能用于在父子进程之间进行通信,并且匿名管道只存在于创建它的进程内存中,进程退出后即被销毁。
- 而有名管道存在文件实体,可以用于任意进程间的通信,并且数据可以持久化,即使创建它的进程退出,其他进程仍然可以使用该管道。
3、信号和信号量的区别?
信号用于异步处理异常或特殊事件,而信号量用于同步进程以安全地访问资源共享资源。
- 信号是一种异步的通知机制,用于通知进程某个事件已经发生。当一个信号发送给一个进程时,操作系统会中断进程的正常流程来处理信号。
- 信号量是一种用于解决多进程同步和互斥问题的工具,主要用于保护共享资源,防止多个进程同时访问。信号量有一个整数值,该值的含义是可以同时访问某个资源的进程数量。当一个进程想要访问资源时,它会尝试递减信号量(P操作)。如果信号量的值大于0,这个操作成功,否则进程就会阻塞,知道信号量变为整数。当进程完成对资源的访问后,它会递增信号量(V操作),允许其他阻塞的进程访问资源。
调度
1、进程的调度算法有哪些?
- 先来先服务(FCFS):按照进程到达的顺序进行调度,先到达的进程先执行。优势是简单、公平,但可能导致长作业等待时间过长,无法适应实时性要求高的场景。
- 最短作业优先(SJF):选择估计运行时间最短的进程优先执行。优势是能够最大程度地减少平均等待时间,但需要准确预测进程的运行时间,这个实现起来会比较困难。
- 轮转调度:按照时间片的方式轮流调度进程执行,每个进程分配一个固定的时间片,时间片用完之后去切换到下一个进程。优势是公平,但对于长作业和实时性要求高的场景可能不够高效。
- 优先级调度:为每个进程分配一个优先级,优先级高的进程先执行。可根据不同的调度策略确定优先级,如静态优先级、动态优先级等。优势是能够根据进程的重要性和紧急程度进行调查,但可能导致优先级低的进程长时间等待。
- 多级反馈队列调度:将进程划分为多个队列,每个队列有不同的优先级和时间片大小,每个队列优先级从高到低,同时优先级越高时间片越短,新的进程会被放入到第一级队列的末尾,按先来先服务的原则排队等待被调度,如果在第一梯队列规定的时间片没有运行完成,则将其转入到第二级队列的末尾,以此类推,直至完成。如果进程运行时,有新进程进入优先级较高的队列,则停止当前运行的进程并将其移入到原队列末尾,接着让较高优先级的进程运行。多级反馈队列调度存在的问题,优先级较低的队列可能会被优先级较高的队列长时间占用,导致优先级较低的进程无法得到执行,从而产生饥饿现象。
锁
1、线程间同步方式有哪一些?
Linux系统提供了五种用于线程间同步的方式:互斥量、读写锁、信号量、自旋锁、条件变量。
- 互斥锁:用于保护共享资源,确保同一时间只有一个线程可以访问该资源。只有获得互斥锁的线程才能进入临界区,其他线程需要等待锁的释放。
- 读写锁:也称为共享-独占锁,允许多个线程同时读取共享资源,但在写操作时需要独占访问。读写锁在读多写少的场景中可以提供更好的并发性能。
- 信号量:用于控制对一组资源的访问。信号量可以允许多个线程同时访问资源,但是需要再访问前进行P操作和在访问结束后进行V操作,以确保资源正确使用。
- 自旋锁:是一种忙等待锁,在获取锁之前,线程会一直获取锁,而不会进入睡眠状态。自旋锁适用于保护临界区较小、锁占用时间短暂的情况。
- 条件变量:用于在线程之间进行条件同步。一个线程可以等待某个条件满足,而另一个线程在满足条件时可以通知等待的线程继续执行。
2、信号量和互斥锁应用场景有什么区别?
信号量(Semaphore)一般以同步的方式对共享资源进行控制,而互斥锁通过互斥的方式对共享资源进行控制。
信号量可以控制有限资源的访问,而互斥锁只能控制一个资源的访问。
互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。
3、自旋锁和互斥锁有什么区别?分别适合哪些应用场景?
互斥锁加锁失败的时候,线程会放弃CPU,陷入到内核态,执行线程切换和CPU上下文切换的过程,切换到其他线程,而自旋锁加锁失败后,线程不会放弃CPU,而是选择忙等待,直到它拿到锁。
自旋锁适用于并发竞争时间短暂的情况,可以减少线程切换和CPU上下文切换的开销。互斥锁适用于并发竞争时间较长或资源争用较激烈的情况,可以避免线程忙等待,但会带来线程切换和上下文切换的开销。
4、悲观锁和乐观锁有什么区别?
悲观锁做事比较悲观,它认为多线程同时修改共享资源的概率比较高,很容易出现冲突,造成数据错乱,所以会每次读写共享资源之前,先要加锁,确保任意时刻只有一个线程才能对数据进行读写操作。
乐观锁做事比较乐观,它假定冲突的概率很低,先修改共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源那么操作完成,如果有发现其他线程已经修改过这个资源,就放弃本次操作,并选择报错、重试策略。
悲观锁适合并发写多和竞争激烈的场景,这种场景下悲观锁可以避免大量的无用的反复尝试等消耗;乐观锁适合读多写少喝并发不激烈的场景,在这些场景下乐观锁不加锁的特点能让性能大幅提高。
5、乐观锁怎么实现?
乐观锁的实现通常基于版本号或时间戳机制,比如CAS(Compare And Swap)算法,它包含三个参数:内存地址、期望值和新值。
CAS操作会比较内存位置的当前值与期望值是否相等,如果相等,则将内存地址的值更新为新值;如果不想等,则说明其他进程或线程已经修改过内存地址的值,就不会修改数据,会根据不同的业务逻辑去选择报错或者重试。
6、操作系统死锁是如何产生的?
什么是死锁?
死锁是指在并发执行的多个进程或线程中,每个进程或线程都在等待其他进程或线程释放资源,导致所有进程或线程都无法继续执行的一种状态。
回答:当两个或以上线程并行执行的时候,争夺资源而造成相互等待的现象,这时候就发生了死锁。死锁产生的四个必要条件:互斥条件、持有并等待条件、不剥夺条件、环路等待条件。只要上述条件之一不满足,就不会发生死锁。
7、如何避免死锁?
加锁顺序、加锁时限、死锁检测。
- 加锁顺序:破坏死锁四个必要条件中任意一个就能避免死锁,最常用的方法就是使用资源有序分配法来破坏坏路等待条件,比如线程1先对A资源加锁,再对B资源加锁,线程2,也使用相同的顺序。
- 加锁时限:在尝试获取锁的时候加一个超时时间,如果超时时间到了,该线程则放弃对该锁的请求。如果一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已获得的锁,然后等待一段随机的时间重试。这段随机等待的时间让其他线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行。
- 死锁检测:当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生。
参考资料
https://xiaolincoding.com/os/4_process/process_base.html
https://xiaolincoding.com/os/4_process/process_commu.html
https://xiaolincoding.com/os/4_process/deadlock.html
https://xiaolincoding.com/os/4_process/pessim_and_optimi_lock.html