第十六章 协程

本文深入探讨了Python中的协程概念,包括与进程和线程的区别,以及协程的优缺点。文章详细介绍了如何通过yield、greenlet、gevent和asyncio实现协程,特别是asyncio的事件循环、协程定义、任务创建和并发执行。通过实例展示了如何在Python中优雅地使用协程实现高并发和异步操作。
摘要由CSDN通过智能技术生成

目录

一、概念

1.1 用户态和内核态

1.2 协程

1.2.1 什么是协程

1.2.2 与进程的区别

1.2.3 与线程的区别

1.2.4 协程的优缺点

二、代码实现

2.1 yield实现协程

2.2 greenlet实现协程 

2.3 gevent实现协程

2.4 asyncio实现协程

2.4.1 定义一个协程

2.4.2 创建一个Task

2.4.3 绑定回调

2.4.4 future与result

2.4.5 阻塞和await

​​​​​​​2.4.6 并发和并行

​​​​​​​2.4.7 协程嵌套

​​​​​​​2.4.8 协程停止

​​​​​​​2.4.9 新线程协程


一、概念

1.1 用户态和内核态

内核态: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序

用户态: 只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取

为什么要有用户态和内核态?

由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 -- 用户态 和 内核态

用户态与内核态的切换

所有用户程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如从硬盘读取数据, 或者从键盘获取输入等. 而唯一可以做这些事情的就是操作系统, 所以此时程序就需要先操作系统请求以程序的名义来执行这些操作.

这时需要一个这样的机制: 用户态程序切换到内核态, 但是不能控制在内核态中执行的指令

这种机制叫系统调用, 在CPU中的实现称之为陷阱指令(Trap Instruction)

它们的工作流程如下:

  1. 用户态程序将一些数据值放在寄存器中, 或者使用参数创建一个堆栈(stack frame), 以此表明需要操作系统提供的服务.
  2. 用户态程序执行陷阱指令
  3. CPU切换到内核态, 并跳到位于内存指定位置的指令, 这些指令是操作系统的一部分, 他们具有内存保护, 不可被用户态程序访问
  4. 这些指令称之为陷阱(trap)或者系统调用处理器(system call handler). 他们会读取程序放入内存的数据参数, 并执行程序请求的服务
  5. 系统调用完成后, 操作系统会重置CPU为用户态并返回系统调用的结果

1.2 协程

1.2.1 什么是协程

协程(Coroutine)是线程的更小切分,又称为“微线程”,是一种用户态的轻量级线程。
线程系统级别的它们由操作系统调度
协程则是程序级别的由程序员根据需要自己调度

在一个线程中会有很多函数,我们把这些函数称为子程序,在子程序执行过程中可以中断去执行别的子程序,而别的子程序也可以中断回来继续执行之前的子程序,这个过程就称为协程。也就是说在同一线程内一段代码在执行过程中会中断然后跳转执行别的代码,接着在之前中断的地方继续开始执行,类似与yield操作。

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

1.2.2 与进程的区别

相同点:
相同点存在于,当我们挂起一个执行流时,我们要保存的东西:
(1)栈,其实在你切换前你的局部变量,以及要函数的调用都需要保存,否则都无法恢复
(2)寄存器状态,这个其实用于当你的执行流恢复后要做什么

而寄存器和栈的结合就可以理解为上下文,上下文切换的理解:
CPU看上去像是在并发的执行多个进程,这是通过处理器在进程之间切换来实现的,操作系统实现这种交错执行的机制称为上下文切换

操作系统保持跟踪进程运行所需的所有状态信息,这种状态,就是上下文。
在任何一个时刻,操作系统都只能执行一个进程代码,当操作系统决定把控制权从当前进程转移到某个新进程时,就会进行上下文切换,即保存当前进程的上下文,恢复新进程的上下文,然后将控制权传递到新进程,新进程就会从它上次停止的地方开始。

不同点:
(1)执行流的调度者不同,进程是内核调度,而协程是在用户态调度,也就是说进程的上下文是在内核态保存恢复的,而协程是在用户态保存恢复的,很显然用户态的代价更低
(2)进程会被强占,而协程不会,也就是说协程如果不主动让出CPU,那么其他的协程,就没有执行的机会。
(3)对内存的占用不同,实际上协程可以只需要4K的栈就足够了,而进程占用的内存要大的多
(4)从操作系统的角度讲,多协程的程序是单进程,单线程

1.2.3 与线程的区别

既然我们上面也说了,协程也被称为微线程,下面对比一下协程和线程:
(1)线程之间需要上下文切换成本相对协程来说是比较高的,尤其在开启线程较多时,但协程的切换成本非常低。
(2)同样的线程的切换更多的是靠操作系统来控制,而协程的执行由我们自己控制。
  协程只是在单一的线程里不同的协程之间切换;其实和线程很像,线程是在一个进程下,不同的线程之间做切换。

1.2.4 协程的优缺点

优点:

(1)无需线程上下文切换的开销,协程避免了无意义的调度,由此可以提高性能(但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力) 
   (执行效率极高,因为子程序切换(函数)不是线程切换,由程序自身控制,没有切换线程的开销。所以与多线程相比,线程的数量越多,协程性能的优势越明显)

(2)无需原子操作锁定及同步的开销
    (不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在控制共享资源时也不需要加锁,因此执行效率高很多。)

(3)方便切换控制流,简化编程模型
(4)高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

缺点:

(1)无法利用多核资源:协程的本质是个单线程,它不能同时将单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上,当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
(2)进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

【注】
协程可以处理IO密集型程序的效率问题,但是处理CPU密集型不是它的长处,如要充分发挥CPU利用率可以结合多进程+协程。

二、代码实现

网络模型有很多中,为了实现高并发也有很多方案,多线程,

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值