这里先给出一些经常使用的知识点简要说明,以便理解后面的文章内容。html
进程的定义:python
进程,是计算机中已运行程序的实体。程序自己只是指令、数据及其组织形式的描述,进程才是程序的真正运行实例。git
线程的定义:github
操做系统可以进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运做单位。golang
进程和线程的关系:redis
一条线程指的是进程中一个单一顺序的控制流,一个进程中能够并发多个线程,每条线程并行执行不一样的任务。
CPU的最小调度单元是线程不是进程,因此单进程多线程也能够利用多核CPU.编程
协程的定义:数组
协程经过在线程中实现调度,避免了陷入内核级别的上下文切换形成的性能损失,进而突破了线程在IO上的性能瓶颈。多线程
协程和线程的关系并发
协程是在语言层面实现对线程的调度,避免了内核级别的上下文消耗。
Python的协程源于yield指令。yield有两个功能:
import asyncio async def compute(x, y): print("Compute %s + %s ..." % (x, y)) await asyncio.sleep(x + y) return x + y async def print_sum(x, y): result = await compute(x, y) print("%s + %s = %s" % (x, y, result)) loop = asyncio.get_event_loop() tasks = [print_sum(1, 2), print_sum(3, 4)] loop.run_until_complete(asyncio.wait(tasks)) loop.close()
协程是对线程的调度,yield相似惰性求值方式能够视为一种流程控制工具,实现协做式多任务,在Python3.5正式引入了async/await
表达式,使得协程正式在语言层面获得支持和优化,大大简化以前的yield写法。
线程是内核进行抢占式的调度的,这样就确保了每一个线程都有执行的机会。
而 coroutine
运行在同一个线程中,由语言的运行时中的 EventLoop
(事件循环)来进行调度。
和大多数语言同样,在 Python 中,协程的调度是非抢占式的,也就是说一个协程必须主动让出执行机会,其余协程才有机会运行。
让出执行的关键字就是 await。也就是说一个协程若是阻塞了,持续不让出 CPU,那么整个线程就卡住了,没有任何并发。
简而言之,任什么时候候只有一个协程正在运行。
PS: 做为服务端,event loop最核心的就是IO多路复用技术,全部来自客户端的请求都由IO多路复用函数来处理;做为客户端,event loop的核心在于利用Future对象延迟执行,并使用send函数激发协程,挂起,等待服务端处理完成返回后再调用CallBack函数继续下面的流程
Go天生在语言层面支持,和Python相似都是采用了关键字,而Go语言使用了go这个关键字,多是想代表协程是Go语言中最重要的特性。
go协程之间的通讯,Go采用了channel关键字。
Go实现了两种并发形式:
Go的CSP并发模型实现:M, P, G : [https://www.cnblogs.com/sunsk...]
package main import ( "fmt" ) //Go 协程(goroutines)和协程(coroutines) //Go 协程意味着并行(或者能够以并行的方式部署),协程通常来讲不是这样的 //Go 协程经过通道来通讯;协程经过让出和恢复操做来通讯 // 进程退出时不会等待并发任务结束,可用通道(channel)阻塞,而后发出退出信号 func main() { jobs := make(chan int) done := make(chan bool) // 结束标志 go func() { for { j, more := <-jobs // 利用more这个值来判断通道是否关闭,若是关闭了,那么more的值为false,而且通知给通道done fmt.Println("----->:", j, more) if more { fmt.Println("received job", j) } 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) // 写完最后的数据,紧接着就close掉 fmt.Println("close(jobs)") }() fmt.Println("sent all jobs") <-done // 让main等待所有协程完成工做 }
经过在函数调用前使用关键字go
,咱们便可让该函数以 goroutine
方式执行。goroutine
是一种 比线程更加轻盈、更省资源的协程。
Go 语言经过系统的线程来多路派遣这些函数的执行,使得 每一个用 go 关键字执行的函数能够运行成为一个单位协程。
当一个协程阻塞的时候,调度器就会自 动把其余协程安排到另外的线程中去执行,从而实现了程序无等待并行化运行。
并且调度的开销很是小,一颗 CPU 调度的规模不下于每秒百万次,这使得咱们可以建立大量的 goroutine
,从而能够很轻松地编写高并发程序,达到咱们想要的目的。 ---- 某书
协程的4种状态
和系统线程之间的映射关系
go的协程本质上仍是系统的线程调用,而Python中的协程是eventloop模型实现,因此虽然都叫协程,但并非一个东西.
Python 中的协程是严格的 1:N 关系,也就是一个线程对应了多个协程。虽然能够实现异步I/O,可是不能有效利用多核(GIL)。
而 Go 中是 M:N 的关系,也就是 N 个协程会映射分配到 M 个线程上,这样带来了两点好处:
PS: Go中不多说起线程或进程,也就是由于上面的缘由.
async
是非抢占式的,一旦开始采用 async
函数,那么你整个程序都必须是 async
的,否则总会有阻塞的地方(一遇阻塞对于没有实现异步特性的库就没法主动让调度器调度其余协程了),也就是说 async
具备传染性。goroutine
是 Go 与生俱来的特性,因此几乎全部库都是能够直接用的,避免了 Python 中须要把全部库重写一遍的问题。goroutine
中不须要显式使用await
交出控制权,可是 Go 也不会严格按照时间片去调度 goroutine
,而是会在可能阻塞的地方插入调度。goroutine
的调度能够看作是半抢占式的。Do not communicate by sharing memory; instead, share memory by communicating.(不要以共享内存的方式来通讯,相反,要经过通讯来共享内存) -- CSP并发模型
erlang和golang都是采用了CSP(Communicating Sequential Processes)模式(Python中的协程是eventloop模型) 可是erlang是基于进程的消息通讯,go是基于goroutine和channel的通讯。 Python和Go都引入了消息调度系统模型,来避免锁的影响和进程/线程开销大的问题。 协程从本质上来讲是一种用户态的线程,不须要系统来执行抢占式调度,而是在语言层面实现线程的调度。 由于协程再也不使用共享内存/数据,而是使用通讯来共享内存/锁,由于在一个超级大系统里具备无数的锁, 共享变量等等会使得整个系统变得无比的臃肿,而经过消息机制来交流,可使得每一个并发的单元都成为一个独立的个体, 拥有本身的变量,单元之间变量并不共享,对于单元的输入输出只有消息。 开发者只须要关心在一个并发单元的输入与输出的影响,而不须要再考虑相似于修改共享内存/数据对其它程序的影响。