Python与Go的协程
协程
协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。
子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。
协程看上去也是子程序,但执行过程中,
在子程序内部可中断
,然后转而执行别的子程序,在适当的时候再返回来接着执行。
注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。
以上说明来自廖雪峰官方网站
协程主要要解决两个问题
使用同步的方式编写异步的代码
在单线程内切换任务
协程线程进程的切换对比
进程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户是无感知的。进程的切换内容包括页全局目录、内核栈、硬件上下文,切换内容保存在内存中。进程切换过程是由“用户态到内核态到用户态”的方式,切换效率低。
线程的切换者是操作系统,切换时机是根据操作系统自己的切换策略,用户无感知。线程的切换内容包括内核栈和硬件上下文。线程切换内容保存在内核栈中。线程切换过程是由“用户态到内核态到用户态”,
切换效率中等。
协程的切换者是用户(编程者或应用程序),切换时机是用户自己的程序所决定的。协程的切换内容是
硬件上下文
,切换内存保存在
用户自己的变量
(用户栈或堆)中。协程的切换过程只有用户态,即没有陷入内核态,因此切换效率高。
python协程
使用
async def time_consume_3(i):
print("start")
# time.sleep() 这是个同步操作 会同步执行
await asyncio.sleep(2)
return i*100
async def middle_3(i):
time_consume_3_return = await time_consume_3(i)
return time_consume_3_return
tasks = [middle_3(i) for i in range(10)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
原理
使用python的yield和yield from 可以暂停函数并在适当的时候返回继续执行该函数的语法特性
优点
和同步调用相比优势是能够并发
和多线程多进程相比优势在于
在线程内的切换操作系统并不能察觉 开销更小,因此能一定程度上突破操作系统的限制,实现更大的并发。
使用同步的方式编写异步的代码 相对来说好阅读
缺点
协程需要主动让出执行 其他协程才有机会执行 意味着一个协程阻塞则整个线程就阻塞了
Go 协程
使用
go func(){
...
}()
原理
go语言的并发也可以用多线程加锁的方式实现 但这里主要讲go通过实现csp(Communicating Sequential Processes)模型中的process 和 channel 两个概念来管理并发。
在process (也就是goroutine)的调度上,go语言使用MPG模型在语言层面上支持。
M指的是Machine,一个M直接关联了一个内核线程。
P指的是Processor,代表了M所需的上下文环境,也是处理用户级代码逻辑的处理器。
G指的是Goroutine,其实本质上也是一种轻量级的线程。
需要解决的问题
这里主要需要解决两个问题
goroutine之间的同步和通信问题
同步使用信号量 通信使用通道
通道原理
goroutine的调度问题,包括怎样分配goroutine运行时间 遇到阻塞如何处理?
go维护每个逻辑处理器(也就是P)的本地队列,以及一个全局队列
在本地队列中的goroutine轮流执行 。一旦goroutine阻塞则脱离当前逻辑处理器,等待阻塞完成添加到全局队列,再分配到某个逻辑处理器的本地队列执行。
两者对比
go的协程和python的协程实现上差别很大
从实现方式上看
python的协程通过事件循环监听任务来实现,而go还是通过调用系统的线程来实现并发。
python的协程与线程是N:1的关系,而go是M:N的关系
从生态上看 go天生支持并发 而python的异步库使用还存在很大限制,数据库,网络请求等库都需要特定的异步库来实现。