进程是系统进行资源分配和调度的一个独立单位,程序段、数据段、PCB三部分组成了进程实体(进程映像),PCB是进程存在的唯一标准
创建态: 操作系统为进程分配资源,初始化PCB
就绪态:运行资源等条件都满足,存储在就绪队列中,等待CPU调度
运行态:CPU正在执行进程
阻塞态:等待某些条件满足,等待消息回复,等待同步锁,sleep等,阻塞队列
终止态 :回收进程拥有的资源,撤销PCB
进程在操作系统内核程序临界区中不能进行调度与切换
临界资源:一个时间段内只允许一个进程使用资源,各进程需要互斥地访问临界资源
临界区:访问临界资源的代码
内核程序临界区:访问某种内核数据结构,如进程的就绪队列(存储各进程的PCB)
进程调度的方式:
进程的切换与过程:进程的调度、切换是有代价的
进程调度算法的相关参数:
调度算法:
算法思想,用于解决什么问题?
算法规则,用于作业(PCB作业)调度还是进程调度?
抢占式还是非抢占式的?
优缺点?是否会导致饥饿?
以下调度算法是适用于当前交互式操作系统
算法思想:综合FCFS、SJF(SPF)、时间片轮转、优先级调度
算法规则:
用于作业/进程调度:用于进程调度
是否可抢占? 抢占式算法。在k级队列的进程运行过程中,若更上级别的队列(1-k-1级)中进入一个新进程,则由于新进程处于优先级高的队列中,因此新进程会抢占处理机,原理运行的进程放回k级队列队尾。
优缺点:对各类型进程相对公平(FCFS的有点);每个新到达的进程都可以很快就得到相应(RR优点);短进程只用较少的时间就可完成(SPF)的有点;不必实现估计进程的运行时间;可灵活地调整对各类进程的偏好程度,比如CPU密集型进程、I/O密集型进程(拓展:可以将因I/O而阻塞的进程重新放回原队列,这样I/O型进程就可以保持较高优先级)
是否会导致饥饿: 会
引入线程之后,进程只作为除CPU之外的系统资源的分配单元(如:打印机,内存地址空间等都是分配给进程的)
线程的是实现方式:
进程和线程的关系:一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。CPU的最小调度单元是线程,所以单进程多线程是可以利用多核CPU的。
多个用户态的线程对应着一个内核线程,程序线程的创建、终止、切换或者同步等线程工作必须自身来完成。python就是这种。虽然可以实现异步,但是不能有效利用多核(GIL)
这种模型直接调用操作系统的内核线程,所有线程的创建、终止、切换、同步等操作,都由内核来完成。C++就是这种
这种线程模型会先创建多个内核级线程,然后用自身的用户级线程去对应创建的多个内核级线程,自身的用户级线程需要本身程序去调度,内核级的线程交给操作系统内核去调度。GO语言就是这种。
python中的多线程因为GIL的存在,并不能利用多核CPU优势,但是在阻塞的系统调用中,如sock.connect(), sock.recv()等耗时的I/O操作,当前的线程会释放GIL,让出处理器。但是单个线程内,阻塞调用上还是阻塞的。除了GIL之外,所有的多线程还有通病,他们都是被OS调用的,调度策略是抢占式的,以保证同等有限级的线程都有机执行,带来的问题就是:并不知道下一刻执行那个线程,也不知道正在执行什么代码,会存在竞态条件
协程通过在线程中实现调度,避免了陷入内核级别的上下文切换造成的性能损失,进而突破了线程在IO上的性能瓶颈。
python的协程源于yield指令
协程式对线程的调度,yield类似惰性求职方式可以视为一种流程控制工具,实现协作式多任务,python3.5引入了async/await表达式,使得协程证实在语言层面得到支持和优化,大大简化之前的yield写法。线程正式在语言层面得到支持和优化。线程是内核进行抢占式调度的,这样就确保每个线程都有执行的机会。而coroutine运行在同一个线程中,有语言层面运行时中的EventLoop(事件循环)来进行调度。在python中协程的调度是非抢占式的,也就是说一个协程必须主动让出执行机会,其他协程才有机会运行。让出执行的关键字 await, 如果一个协程阻塞了,持续不让出CPU处理机,那么整个线程就卡住了,没有任何并发。
PS: 作为服务端,event loop最核心的就是I/O多路复用技术,所有来自客户端的请求都由I/O多路复用函数来处理;作为客户端,event loop的核心在于Future对象延迟执行,并使用send函数激发协程,挂起,等待服务端处理完成返回后再调用Callback函数继续执行。[python 协程与go协程的区别]
Go 天生在语言层面支持,和python类似都是用关键字,而GO语言使用了go关键字,go协程之间的通信,采用了channel关键字。
go实现了两种并发形式:
Copy
package main import ("fmt") func main() { jobs := make(chan int) done := make(chan bool) // end flag go func() { for { j, ok := <- jobs fmt.Println("---->:", j, ok) if ok { fmt.Println("received job") } else { fmt.Println("end received jobs") done <- true return } } }() go func() { for j:= 1; j <= 3; j++ { jobs <-j fmt.Println("sent job", j) } close(jobs) fmt.Println("close(jobs)") }() fmt.Println("sent all jobs") <-done // 阻塞 让main等待协程完成 }
Go的CSP并发模型是通过goroutine 和 channel来实现的。
协程本质上来说是一种用户态的线程,不需要系统来执行抢占式调度,而是在语言测个面实现线程的调度。
并发:Do not communicate by sharing memory; instead, share memory by communicate.
Actor模型和CSP模型的区别:
好文推荐:Go/Python/Erlang编程语言对比分析及示例
一个M会对应一个内核线程,一个M也会连接一个上下文P,一个上下文P相当于一个“处理器”,一个上下文连接一个或者多个Goroutine。P(Processor)的数量是在启动时被设置为环境变量GOMAXPROCS的值,或者通过运行时调用函数runtime.GOMAXPROCS()
进行设置
erlang和golang都是采用CSP模型,python中协程是eventloop模型。但是erlang是基于进程的消息通信,go是基于goroutine和channel通信。
python和golang都引入了消息调度系统模型,来避免锁的影响和进程线程的开销问题。
计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决 -- G-P-M模型正是此理论践行者,此理论也用到了python的asyncio对地狱回调的处理上(使用Task+Future避免回调嵌套),是不是巧合?
其实异步≈可中断的函数+事件循环+回调,go和python都把嵌套结构转换成列表结构有点像算法中的递归转迭代.
调度器在计算机中是分配工作时所需要的资源,Linux的调度是CPU找到可运行的线程,Go的调度是为M线程找到P(内存,执行票据)和可运行的G(协程)
Go协程是轻量级的,栈初始2KB(OS操作系统的线程一般都是固有的栈内存2M), 调度不涉及系统调用,用户函数调用前会检查栈空间是否足够,不够的话,会进行站扩容,栈大小限制可以达到1GB。
Go的网络操作是封装了epoll, 为NonBlocking模式,切换协程不阻塞线程。
Go语言相比起其他语言的优势在于OS线程是由OS内核来调度的,goroutine
则是由Go运行时(runtime)自己的调度器调度的,这个调度器使用一个称为m:n调度的技术(复用/调度m个goroutine到n个OS线程)。 其一大特点是goroutine的调度是在用户态下完成的, 不涉及内核态与用户态之间的频繁切换,包括内存的分配与释放,都是在用户态维护着一块大的内存池, 不直接调用系统的malloc函数(除非内存池需要改变),成本比调度OS线程低很多。 另一方面充分利用了多核的硬件资源,近似的把若干goroutine均分在物理线程上, 再加上本身goroutine的超轻量,以上种种保证了go调度方面的性能。
最后,有空看看柠檬观看网,最新的国产视频。