进程和线程
- 进程是计算机资源分配的最小单位、线程是CPU执行的最小单位
- 进程拥有完整的资源,线程一般只独享部分资源
- 线程属于进程
- 进程是程序的一次执行,进程之间的内存是很难共享,而线程之间的内存很容易共享
- 线程挂掉后可能会导致其他线程挂掉,一个进程挂掉后不会影响到其他进程
为什么Java处理并发采用多线程而不采用多进程
- 由于进程是资源分配的最小单位,每个进程的创建、销毁、切换都会销毁大量的时间和空间所以进程的数量不能太多
- 而线程基本不拥有系统资源,只有一些运行时必不可少的资源,可以减少程序并发执行的时间和空间,具有更好的并发性
进程
进程的状态
- 创建状态
- 就绪态
- 运行态
- 阻塞态
- 结束状态
- 挂起状态
进程被阻塞后是不能主动恢复就绪的,必须是由其他进程唤醒
进程控制块PCB
PCB中的信息
- 进程唯一标识
- 进程当前状态
- 持有资源
- 内存空间
进程终止流程
- 查找进程的PCB
- 如果进程处于执行态,则立即终止
- 终止其子进程
- 将该进程拥有的资源归还给父进程和操作系统
- 删除其PCB
Linux进程调度算法
先来先服务
先到的进程先执行,不论执行时间长短
短作业优先
系统按当前等待执行的进程需要执行的时间来排,作业执行时间越短优先级越高,先执行
时间片轮转
给每个进程分配等量的时间片,进程
优先级调度算法
优先级调度算法是指作业根据优先级依次执行。
优先级调度算法有两种,一种是非抢占式的(CPU允许当前在执行的进程优先级低于等待的进程)
另一种是抢占式的(优先级高的进程可以抢占正在执行的低优先级进程)
多级反馈队列
有多个就绪队列,最上层的就绪队列时间片较小,下层就绪队列时间片依次增大且队列优先级依次降低。进程都会先进入上层就绪队列,进程执行一次后如果没有结束将会进入下一级队列等待。最底层队列采用有时间片轮转依次执行
进程间通信方式
进程间通信方式主要有7种
管道通信
管道通信是半双工的(数据是单向传输的),双方都需要通信时需要建立两个管道。
管道只能用于父子进程和兄弟进程之间
管道实际上就是在内核中的一块数据缓冲区,传输方每次将数据置于管道尾部,接收方每次从管道头部获取数据
管道的缺点
- 只支持单向数据流动
- 只能用于父子/亲缘进程
- 缓冲区有大小
- 管道传输的数据是无格式的字节流,双方要约定数据格式
线程
线程是比进程更小的执行单位,线程是属于进程的。其作用就是使得程序能同时执行多个任务。
为什么不采用多进程
因为进程本身比线程更庞大,CPU在切换上下文时需要消耗的时间更长,并且普通进程之间是不共享内存空间的。当一个程序需要协作完成任务时,多进程就很困难了。而线程不仅需要的资源更小,CPU切换线程时消耗的时间也小并且线程之间是可以共享内存空间的。
线程的缺点
线程崩溃后可能会影响其他线程从而导致整个进程崩溃
线程切换
线程和进程一样也是有状态的。线程在切换时如果切换的两个线程属于同一个进程,只需要切换部分线程不共享的数据,所属进程的资源不需要切换。如果切换的两个线程属于不同进程,切换过程等于进程切换。
Linux命令
Kill
作用:关闭进程
kill -9和kill区别
kill默认信号为 kill -15
kill -15: 系统像程序发送一个信号,程序接收到信号后如何处理取决于程序自身。程序的处理方式有三种
- 立即停止
- 释放资源后停止
- 忽略信号,继续执行
kill -9:强制程序退出,这种退出程序是没有办法处理退出工作的,很可能造成数据丢失和无法恢复等情况
IO
DMA
DMA 的中文名称是直接内存访问,它意味着 CPU 授予 I/O 模块权限在不涉及 CPU 的情况下读取或写入内存。也就是 DMA 可以不需要 CPU 的参与。这个过程由称为 DMA 控制器(DMAC)的芯片管理。由于 DMA 设备可以直接在内存之间传输数据,而不是使用 CPU 作为中介,因此可以缓解总线上的拥塞。DMA 通过允许 CPU 执行任务,同时 DMA 系统通过系统和内存总线传输数据来提高系统并发性。
DMA的作用就是减少CPU对I/O的处理,使得用户进程需要进行I/O时可以直接由DMA对数据进行拷贝到内核态,数据读取完成后再通知CPU处理,数据拷贝期间CPU就可以执行其他任务。
- 用户进程向操作系统发起一次read读取请求
- 开始系统调用(用户态切换到内核态)
- 用户进程进入阻塞态
- 操作系统接收到I/O请求后交给DMA控制器
- DMA控制器将请求发给磁盘请求读取数据
- 磁盘将数据拷贝到磁盘缓冲区中并通知DMA
- DMA将数据拷贝内核缓冲区中
- 拷贝完成后发送中断信号给CPU
- CPU再将数据拷贝到用户态
- 系统调用结束(内核态切换回用户态)
零拷贝技术
服务端如果提供了文件传输功能,传统的工作流程是
- 将磁盘上的文件读取出来
- 通过网络协议发送到客户端
- DMA将磁盘数据拷贝到内核中
- CPU将内核中的数据拷贝到用户态
- CPU将用户态的数据拷贝到内核态
- DMA将数据拷贝到网卡中发送
整个流程中会涉及到两次系统调用(两次系统调用对应四次上下文切换),四次数据拷贝。
我们可以发现从CPU将内核数据拷贝到用户数据,再将用户数据拷贝内核,这两次拷贝是没有必要的
sendfile
Linux内核版本2.1开始提供了一种专门用于发送文件的系统调用函数sendfile()
用户进程发送一次文件只需要一次系统调用。并且它会直接将内核态的数据拷贝到socket缓冲区中, 再直接从内核态将数据拷贝到socket中,最后再将数据发送到Mac中。
这种方式只需要发起一次系统调用(两次上下文切换),三次数据拷贝。
如歌对应的网卡支持DMA技术的话,甚至只需要两次数据拷贝即可完成(硬盘到内核,内核到网卡)
Tip
- 零拷贝技术不适合用来传输大文件(GB级别)
大文件传输
零拷贝技术会使用PageCache(磁盘数据的缓存),而PageCache使用了预读功能
预读功能
由于空间局部性原理,PageCache在读取数据时,会多读一部分数据到PageCache中,这样如果后续使用到了这部分数据,就可以减少一次IO,并且这个优化在大部分情况下是有效的。
但是在传输大文件时,因为文件本身很大,PageCache被大文件占满,导致其他热点文件无法使用PageCache,这样会降低磁盘读写性能。所以在传输大文件时避免使用零拷贝
大文件传输如何实现
使用异步IO+直接IO
异步IO就是指用户发起一次系统调用时指定数据准备好后需要完成的操作,内核直接将磁盘数据拷贝到用户空间中。
虚拟内存
虚拟内存是指,程序中使用的内存地址为虚拟的内存地址,程序执行时由操作系统来将程序中的逻辑地址映射为真实的物理内存地址。这样的好处就是将操作系统中多个进程的内存空间隔离。
虚拟内存中地址映射主要由两种方式 内存分段、内存分页
页面置换算法
先进先出
每次淘汰进入时间最早的页面(相当于维护一个先进先出队列),这种算法不好因为先进来的页面后面使用的概率比较大。
LRU最近最少使用
每次淘汰最近最少使用的页面。(维护一个链表,节点被访问后移动到链表尾部,需要置换页面时只需要将头部的页面移除即可)
缺点:维护开销大
LFU最不常用
每个页面一个计数器,被访问时计数器+1。需要置换页面时只需要将计数器值最小的页面置换出去
时钟置换
它是LRU和FIFO的折衷。内存中所有页面都通过链接指针链接成一个循环队列,需要置换时,循环检测队列中是否存在未被访问的页面。