进程有多少种状态,如何转换
- 创建:一个进程启动,首先进入创建状态,需要获取系统资源创建进程管理科PCB完成资源分配。
- 就绪态:在创建完成后,进程已经准备好,处于就绪状态,但是还未获得处理器资源,无法运行。
- 运行态:获取处理器资源,被系统调度,当具有时间片开始进入运行状态。如果进程的时间片用完就进入就绪状态。
- 阻塞状态:在运行状态期间,如果进行了阻塞的操作,此时进程暂时无法操作就进入到了阻塞状态,在这些操作完成后就进入就绪状态。等待再次获取处理器资源,被系统调度,当具有时间片就进入运行状态。
- 终止状态:进程结束或者被系统终止,进入终止状态。
线程的通信方式
线程间无需特别的手段进行通信,因为线程间可以共享一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段等。
只需要将数据复制到共享(全局或堆)变量中即可。
不过,要考虑线程的同步和互斥,应用的技术有:
- 信号:Linux中使用pthread_kill()函数堆线程发信号。
- 互斥锁、读写锁、自旋锁。互斥锁确保同一时间只能有一个线程访问共享资源,当锁被占用时试图对其加锁的线程都进入阻塞状态,当锁释放时哪个等待线程能获得取决于内核的调度。读写锁当以写模式加锁时而处于写状态时,任何试图加锁的线程都阻塞,当以读状态模式加锁时而处于读状态时读线程不阻塞,写线程阻塞,读模式共享,写模式互斥。自旋锁上锁受阻时,线程不阻塞而是在循环中查询能否获得该锁,没有线程的切换因而没有切换开销,不过CPU的霸占会导致CPU资源的浪费,所以自旋锁适用于多个处理器或者适用于锁被持有时间短而不希望在线程切换时产生开销的情况。
- 条件变量,条件变量可以以原子的方式阻塞进程,直到某个特定条件为真。对条件的测试是在互斥锁的保护下进行的,条件变量始终与互斥锁一起使用。
- 信号量 信号量实际上是一个非负的整数计数器,用来实现对公共资源的控制。在公共资源增加的时候,信号量就增加;公共资源减少的时候,信号量就减少;只有当信号量的值大于0的时候,才能访问信号量所代表的公共资源。
进程和线程的区别
- 进程有独立的地址空间,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间。
- 进程和线程切换时,需要切换进程和线程的上下文,进程的上下文切换时间开销远远大于线程上下文切换时间,耗费资源较大,效率要差一些。
- 进程的并发性较低,线程的并发性较高。
- 每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
- 系统在运行的时候会为每个进程分配不同的内存空间,而对于线程而言,系统不会为线程分配内存,线程组之间共享资源。
- 进程适用于需要独立崩溃保护的任务,一个进程崩溃不会影响其它进程。
- 线程适用于需要轻量级任务调度的应用,一个线程崩溃可能会影响同一进程内的其它线程。
线程和协程的区别
- 线程是操作系统的资源,线程的创建、切换、停止等都非常消耗资源,而创建协程不需要调用操作系统的功能,编程语言自身就能完成,所以协议也称为用户态线程,比线程轻量很多。
- 线程在多核环境下能做到真正意义上的并行,而协程是为并发而产生的。
- 一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行;
- 线程进程都是同步机制,而协程则是异步;
- 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力;
- 操作系统对于线程开辟数量限制在千的级别,而协程可以达到上万的级别。
死锁,产生的必要条件,产生的原因,怎么预防死锁
死锁是指在两个或多个进程之间,每个进程都在等待其它进程释放资源,从而导致这些进程都无法继续执行的现象。这种情况会导致系统资源被无限期占用,进程无法推进。
死锁产生的必要条件
- 互斥条件:资源不能被多个进程同时使用,即某个资源一次只能被一个进程占用。
- 占有并等待:线程已经占有了一些资源,同时还在等待获取其它进程占有的资源。
- 不剥夺条件:已经分配给某个进程的资源,不能被强行剥夺,只能由该进程自行释放。
- 循环等待条件:存在一个进程循环等待的环形链,即进程集合{P1,P2,…,Pn}中,P1等待P2占有的资源,P2等待P3占有的资源,Pn等待P1占有的资源。
死锁产生的原因
死锁通常由以下原因引起:
系统资源不足:资源有限且不可共享,进程竞争资源,导致某些资源被占用且无法释放。
资源分配不当:系统对资源的分配方式不合理,可能会造成资源分配和释放的顺序不当。
进程间相互等待:进程之间的资源需求相互依赖,造成循环等待。
预防死锁的方法
死锁预防、死锁避免和死锁检测与恢复
死锁预防
- 破坏互斥条件:尽量将资源设计为可以共享的,减少互斥资源的使用。
- 破坏占有并等待条件:规定进程在请求资源时,必须一次性全部请求到,或者在申请新的资源前释放当前占用的资源。
- 破坏不剥夺条件:允许系统强行剥夺某些资源,将资源重新分配给需要的进程。
- 破坏循环等待条件:对所有资源进行编号,规定进程按一定顺序请求资源,保证资源申请的线性顺序,避免形成环形等待。
死锁避免
银行家算法:一种动态资源分配算法,在每次资源请求时,模拟资源分配情况,判断是否会进入不安全状态,只有在不会进入不安全状态时,才分配资源。
死锁检测与恢复
死锁检测:系统定期检测资源分配状态,判断是否存在死锁。常用的方法有资源分配图(Resource Allocation Graph, RAG)和基于等待图的检测算法。
死锁恢复:一旦检测到死锁,通过采取措施恢复系统:
终止进程:选择并终止某些进程,以释放资源。
资源剥夺:强制剥夺某些进程占有的资源,并将资源重新分配给其他进程。
回滚:将进程恢复到某个安全状态(通常需要系统支持事务处理和检查点)。
select的原理以及缺点
select是一种IO多路复用技术。
它的主旨思想:
- 首先要构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中,这个文件描述符列表数据类型为fd_set,它是一个整型数组,总共1024个比特位,每一个比特位代表一个文件描述符的状态。比如当需要select检测时,这一位为0就表示不检测对应的文件描述符的事件,为1表示检测对应的文件描述符的事件。
- 调用select()系统调用,监听该列表中的文件描述符的事件,这个函数是阻塞的,直到这些描述符中的一个或者多个进行I/O操作时,该函数才返回,并修改文件描述符的列表中对应的值,0 表示没有检测到该事件,1 表示检测到该事件。
- select() 返回时,会告诉进程有多少描述符要进行 I/O 操作,接下来遍历文件描述符的列表进行 I/O 操作。
select的缺点:
- 每次调用select时,需要把fd从用户态拷贝到内核态,这个开销在fd很多时会很大。
- 每次调用select都需要在内核遍历传递进来的fd,这个开销在fd很多时也很大。
- select支持的文件描述符数量太小,默认是1024(由fd_set决定)
- 文件描述符集合不能重用,因为内核每次检测到事件都会修改,所以每次都需要重置;
- 每次 select 返回后,只能知道有几个 fd 发生了事件,但是具体哪几个还需要遍历文件描述符集合进一步判断。
epoll原理
- 调用epoll_create()会在内核中创建一个event_poll结构体数据,称为poll对象,在这个结构体中有 2 个比较重要的数据成员,一个是需要检测的文件描述符的信息 struct_root rbr(红黑树),还有一个是就绪列表struct list_head rdlist,存放检测到数据发送改变的文件描述符信息(双向链表);
- 调用 epoll_ctrl() 可以向 epoll 对象中添加、删除、修改要监听的文件描述符及事件;
- 调用 epoll_wt() 可以让内核去检测就绪的事件,并将就绪的事件放到就绪列表中并返回,通过返回的事件数组做进一步的事件处理。 epoll 的两种工作模式:
写时拷贝
当一个进程创建一个子进程时,操作系统通常会为子进程复制父进程的所有资源(包括内存空间,文件描述符等)。
但是,直接复制所有资源会带来很大的开销。
- 在创建子进程时,父进程和子进程共享相同的资源,而不是立即复制。
- 所有共享的资源被标记为只读。当任何一个进程尝试修改资源时,会触发一个页面错误。
- 在页面错误发生时,操作系统才会真正复制资源,将其标记为可写。这样,每个进程最终会拥有自己的资源副本,而没有修改资源的进程仍然共享原始资源。
分段和分页
分段
将用户程序地址空间分为若干个大小不等的段,每段可以定义一组相对完整的逻辑信息。存储分配时,以段为单位,段与段在内存中可以不相邻接,实现了离散分配。分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间,并且有助于共享和保护。
分页
用户程序的地址空间被划分成若干固定大小的区域,称为“页”,相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配。分页主要用于实现虚拟内存,从而获得更大的地址空间。
共享内存
共享内存是进程间通信的一种方式。不同进程之间共享的内存为同一段物理内存,所有的进程都可以访问共享内存中的地址。
如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其它进程。
共享内存的优点:因为所以进程共享同一块内存,共享内存在各种进程间通信方式中具有最高的效率。访问共享内核和访问进程独有的内存区域一样快,并不需要通过系统调用或者其他需要切入内核的过程来完成。同时它也避免了对数据的各种不必要的复制。
但是共享内存没有同步机制,这使得我们在使用共享内存进行进程之间的通信时,往往需要借助其他手段来保证进程之间的同步工作。
虚拟内存和物理内存
物理内存
物理内存也称为主存,实际安装在计算机的内存硬件设备,如RAM(随机存取存储器)。物理内存用于存储当前正在执行的程序和数据。
- 物理内存,以前程序寻址用的是物理地址,范围有限,取决于CPU的地址线条数。比如32位平台下,寻址范围是4G。这是固定的,如果没有虚拟内存,且每次开启一个进程都给4G物理内存,内存显然不够。
条件变量
条件变量是同步原语,用于多线程编程中,使线程能够等待某个条件变为真。
条件变量通常与互斥锁一起使用,来实现复杂的线程同步机制。
- wait:线程调用等待操作时,将自己挂起,直到被唤醒。在等待期间,线程会释放关联的互斥锁,以便其它线程能够修改条件。
- signal:唤醒一个等待在条件变量上的线程。
- brodcast:唤醒所有等待在条件变量上的线程。
I/O多路复用
I/O多路复用是一种使得程序能够同时监听多个文件描述符的技术,从而提高程序的性能。
I/O多路复用能够在单个线程中,通过监视多个I/O流的状态来同时管理多个I/O流,一旦检测到某个文件描述符上我们关心的事件发生,能够通知程序进行相应的读写操作。
Linux下实现I/O复用的系统调用有:select、poll、epoll。
- select主旨思想:构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中,这个文件描述符的列表数据类型为fd_set,是一个整型数组,总共1024个比特位,每一个比特位代表一个文件描述符的状态。比如当需要select检测时,这一位为0就表示不检测对应的文件描述符的事件,为1表示检测对应的文件描述符的事件。监听该列表中的文件描述符的事件,这个函数是阻塞的,直到描述符中的一个或者多个进行I/O操作时,该函数才返回,并修改文件描述符的列表中对应的值,0表示没有检测到该事件,1表示检测到该事件。
- poll原理和select类似,但是poll支持的文件描述符没有限制。