python中的协程及实现

简介: 1.协程的概念:协程是一种用户态的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来的时候,恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每当程序切换回来时,就进入上一次离开时程序所处的代码段。

1.协程的概念:

协程是一种用户态的轻量级线程。协程拥有自己的寄存器上下文和栈。

协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来的时候,恢复先前保存的寄存器上下文和栈。

因此,协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每当程序切换回来时,就进入上一次离开时程序所处的代码段。

综合起来,协程的定义就是:

  1. 必须在只有一个单线程里实现并发
  2. 修改共享数据不需加锁
  3. 用户程序里保存多个控制流的上下文栈
  4. 一个协程遇到IO操作自动切换到其它协程

2.yield实现的协程

传统的生产者-消费者模型是一个线程生成消息,一个线程取得消息,能过锁机制控制队列和等待,但一不小心就有可能死锁。

如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换加生产者继续生产,效率较高。

代码如下:

import time

def consumer():
    """
    使用yield生成一个generator生成器
    :return:
    """
    r = " "
    while True:
        # yield接收到变量r,处理之后再把结果返回。函数执行到这一步的时候,函数会停留在这一行上,
        #当别的函数执行next()语句或者generator.send()语句来激活这一句,本函数就会
        #从yield代码的下一行开始继续执行,直到下一次程序循环到yield这里。
        n = yield r
        print("[consumer]<-- %s" % n)
        time.sleep(1)
        r = "ok"

def producer(c):
    next(c)     #启动调用consumer()函数中的生成器
    n = 0
    while n < 10:
        n += 1
        print("[producer]-->%s" % n)
        #生产者生产产品,通过c.send()把程序切换到consumer函数执行
        cr = c.send(n)
        print("[producer] consumer return:%s" % cr)
    c.close()

if __name__ == "__main__":
    c1 = consumer()
    producer(c1)

执行结果:

[producer]--> 1
[consumer]<-- 1
[producer] consumer return:ok
[producer]--> 2
[consumer]<-- 2
[producer] consumer return:ok
[producer]--> 3
[consumer]<-- 3
[producer] consumer return:ok
...     #中间省略
[producer]--> 9
[consumer]<-- 9
[producer] consumer return:ok
[producer]--> 10
[consumer]<-- 10
[producer] consumer return:ok

整个流程是由一个线程执行,producer和consumer协作完成任务,所以称为协程,而不是线程中的抢占式多任务。

基于协程的定义,刚才使用yield实现的协程并不算合格的协程。

3.由greenlet模块实现的协程

greenlet机制的主要思想是:生成器函数或者协程函数中的yield语句挂起函数的执行,直到稍后使用next()或send()操作进行恢复主止。可以使用一个调度器循环在一组生成器函数之间协作多个任务。greenlet是python中实现协程的一个模块。

使用方式 :

from greenlet import greenlet
import time

def func1():    
    print("func1,ok1---->",time.ctime())
    gr2.switch()    #程序会切换到func2执行
    time.sleep(5)   #休眠5s
    print("func1,ok2---->",time.ctime())
    gr2.switch()    #程序又会切换到func2执行

def func2():
    print("func2,ok1---->",time.ctime())
    gr1.switch()    #func2执行到这里会切换回func1执行
    time.sleep(3)   #休眠3s
    print("func2,ok2---->",time.ctime())

gr1=greenlet(func1)
gr2=greenlet(func2)

gr1.switch()

程序执行流程:

1.程序先运行func1,打印第一句话。
2.func1运行到gr2.switch()这里时,会切换到func2执行,func2函数打印第一句话。
3.func2执行到gr1.switch()这里时,又切换回func1函数的time.sleep(5)执行,func1函数会休眠5s。
4.func1先打印第二句话,执行到gr2.switch()这一句时,再次切换回func2函数。
5.func2函数休眠3s,打印func2函数的第二句话,程序执行完毕。

程序执行结果:

func1,ok1----> Fri Jul 21 16:27:11 2017
func2,ok1----> Fri Jul 21 16:27:11 2017
func1,ok2----> Fri Jul 21 16:27:16 2017
func2,ok2----> Fri Jul 21 16:27:19 2017

4.基于greenlet框架,gevent模块实现协程

python通过yield提供了对协程的基本支持,但是不完全。第三方的gevent模块提供了协程支持。

gevent是第三方库,通过greenlet实现协程。
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent自动切换协程,就保证总有greenlet在运行,而不是等待IO。

代码如下:

import gevent,time

def func1():
    print("running in func1--",time.ctime())
    time.sleep(2)
    print("running in func1 again--",time.ctime())

def func2():
    print("running in func2--",time.ctime())
    time.sleep(2)
    print("running in func2 again--",time.ctime())

t1=time.time()
g1=gevent.spawn(func1)
g2=gevent.spawn(func2)
gevent.joinall([g1,g2])
t2=time.time()
print("cost time:",t2-t1)

程序执行结果:

running in func1-- Fri Jul 21 17:20:17 2017
running in func1 again-- Fri Jul 21 17:20:19 2017
running in func2-- Fri Jul 21 17:20:19 2017
running in func2 again-- Fri Jul 21 17:20:21 2017
cost time: 4.007229328155518

可以看到程序是按顺序执行的。修改程序,使用gevent.sleep()使程序按协程方式执行。

修改后的代码如下:

import gevent,time

def func1():
    print("running in func1--",time.ctime())
    gevent.sleep(2)
    print("running in func1 again--",time.ctime())

def func2():
    print("running in func2--",time.ctime())
    gevent.sleep(2)
    print("running in func2 again--",time.ctime())

t1=time.time()
g1=gevent.spawn(func1)
g2=gevent.spawn(func2)
gevent.joinall([g1,g2])
t2=time.time()

print("cost time:",t2-t1)

程序执行结果:

running in func1-- Fri Jul 21 17:17:00 2017
running in func2-- Fri Jul 21 17:17:00 2017
running in func1 again-- Fri Jul 21 17:17:02 2017
running in func2 again-- Fri Jul 21 17:17:02 2017
cost time: 2.0051145553588867

这样,程序会先执行func1接着执行的是func2,再切换回func1执行。
这种方式可以使原本需要4s才能执行完成的程序只需要执行2s就可以了。

gevent.spawn()方法spawn一些任务,然后通过gevent.joinall将任务加入协程执行队列中等待执行。

5.协程的优点:

无需线程上下文切换造成的资源的浪费。
无需原子操作锁定及同步的开销。
方便切换控制流,简化编程模型。
高并发及高扩展性加低成本:一个CPU支持上万的协程都可以,于高并发处理。

6.协程的缺点:

无法利用多核资源,协程的本质是单个线程,不能同时使用多核CPU。
协程需要与进程配合才能运行在多CPU上。
程序一旦阻塞,会阻塞整个代码段。
目录
相关文章
|
2月前
|
调度 Python
python知识点100篇系列(20)-python协程与异步编程asyncio
【10月更文挑战第8天】协程(Coroutine)是一种用户态内的上下文切换技术,通过单线程实现代码块间的切换执行。Python中实现协程的方法包括yield、asyncio模块及async/await关键字。其中,async/await结合asyncio模块可更便捷地编写和管理协程,支持异步IO操作,提高程序并发性能。协程函数、协程对象、Task对象等是其核心概念。
|
27天前
|
NoSQL 关系型数据库 MySQL
python协程+异步总结!
本文介绍了Python中的协程、asyncio模块以及异步编程的相关知识。首先解释了协程的概念和实现方法,包括greenlet、yield关键字、asyncio装饰器和async/await关键字。接着详细讲解了协程的意义和应用场景,如提高IO密集型任务的性能。文章还介绍了事件循环、Task对象、Future对象等核心概念,并提供了多个实战案例,包括异步Redis、MySQL操作、FastAPI框架和异步爬虫。最后提到了uvloop作为asyncio的高性能替代方案。通过这些内容,读者可以全面了解和掌握Python中的异步编程技术。
43 0
|
27天前
|
数据采集 缓存 程序员
python协程使用教程
1. **协程**:介绍了协程的概念、与子程序的区别、优缺点,以及如何在 Python 中使用协程。 2. **同步与异步**:解释了同步与异步的概念,通过示例代码展示了同步和异步处理的区别和应用场景。 3. **asyncio 模块**:详细介绍了 asyncio 模块的概述、基本使用、多任务处理、Task 概念及用法、协程嵌套与返回值等。 4. **aiohttp 与 aiofiles**:讲解了 aiohttp 模块的安装与使用,包括客户端和服务器端的简单实例、URL 参数传递、响应内容读取、自定义请求等。同时介绍了 aiofiles 模块的安装与使用,包括文件读写和异步迭代
36 0
|
2月前
|
数据处理 Python
深入探索:Python中的并发编程新纪元——协程与异步函数解析
深入探索:Python中的并发编程新纪元——协程与异步函数解析
28 3
|
3月前
|
Python
Python中的异步编程与协程实践
【9月更文挑战第28天】本文旨在通过一个简单易懂的示例,介绍如何在Python中利用asyncio库实现异步编程和协程。我们将通过代码示例来展示如何编写高效的并发程序,并解释背后的原理。
|
3月前
|
数据库 开发者 Python
实战指南:用Python协程与异步函数优化高性能Web应用
在快速发展的Web开发领域,高性能与高效响应是衡量应用质量的重要标准。随着Python在Web开发中的广泛应用,如何利用Python的协程(Coroutine)与异步函数(Async Functions)特性来优化Web应用的性能,成为了许多开发者关注的焦点。本文将从实战角度出发,通过具体案例展示如何运用这些技术来提升Web应用的响应速度和吞吐量。
30 1
|
3月前
|
调度 Python
揭秘Python并发编程核心:深入理解协程与异步函数的工作原理
在Python异步编程领域,协程与异步函数成为处理并发任务的关键工具。协程(微线程)比操作系统线程更轻量级,通过`async def`定义并在遇到`await`表达式时暂停执行。异步函数利用`await`实现任务间的切换。事件循环作为异步编程的核心,负责调度任务;`asyncio`库提供了事件循环的管理。Future对象则优雅地处理异步结果。掌握这些概念,可使代码更高效、简洁且易于维护。
26 1
|
3月前
|
调度 开发者 Python
探索Python中的异步编程:理解asyncio和协程
【9月更文挑战第22天】在现代软件工程中,异步编程是提升应用性能的关键技术之一。本文将深入探讨Python语言中的异步编程模型,特别是asyncio库的使用和协程的概念。我们将了解如何通过事件循环和任务来处理并发操作,以及如何用协程来编写非阻塞的代码。文章不仅会介绍理论知识,还会通过实际的代码示例展示如何在Python中实现高效的异步操作。
|
2月前
|
数据采集 调度 Python
Python编程异步爬虫——协程的基本原理(一)
Python编程异步爬虫——协程的基本原理(一)
|
2月前
|
数据采集 Python
Python编程异步爬虫——协程的基本原理(二)
Python编程异步爬虫——协程的基本原理(二)