python之协程

本文介绍了Python中的协程,对比了协程与多线程、多进程的区别,并强调了协程在执行效率、同步机制和并发处理上的优势。文中还通过生产者-消费者模型展示了协程的应用,探讨了合格协程的定义,并提到了Python中greenlet和gevent库在协程实现上的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

协程

了解协程之前,需要先了解多线程和多进程以及函数的工作模式:进程与线程

对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。

函数(子程序),在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。

def A():
    print('a')
    B()
    print('aa')
    return

def B():
    print('b')
    C()
    print('bb')
    return

def C():
    print('c')
    return

所以函数调用是通过栈实现的。函数调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和函数不同。

协程,又称微线程,纤程。英文名Coroutine。

协程看上去也是函数,但执行过程中,在函数内部可中断,然后转而执行别的函数,在适当的时候再返回来接着执行。

注意,在一个函数中中断,去执行其他函数,这并不是使用函数调用。有点类似CPU的中断。比如函数A、B:

def A():
    print(1)
    print(2)
    print(3)
def B():
    print('x')
    print('y')
    print('z')

假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:1,2,x,y,3,z。

在A中没有调用B,所以协程看起来有点像多线程,但协程的特点在于是一个线程执行

协程是一种用户态的轻量级线程。

函数就是协程的一种特例——不切换的协程。”

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

 

那么和多线程比,协程有何优势和劣势

优势:

1、最大的优势就是协程极高的执行效率。因为函数切换不同于线程切换,函数切换是由程序自身控制,因此没有线程切换的开销和多线程相比,线程数量越多,协程的性能优势就越明显。

2、不需要多线程的锁机制,减少了锁定及同步的开销因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

3、方便切换控制流,简化编程模型

4、高并发+高扩展性+低成本一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

 

缺点:

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

2、进行阻塞操作(如IO时)会阻塞掉整个程序

 

例子:

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

如果改用协程的方法,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产:

import time

def consumer(name):
    while True:
        food = yield
        print('%s吃了%s号食物'%(name, food))

def producter(name):
    print('%s开始做吃的了'%name)
    c1 = consumer('a')
    c2 = consumer('b')
    c1.__next__()   #第一次要next一下才能走到生成器的yield位置
    c2.__next__()
    for i in range(100):
        print('----------------------')
        print('做好了%s号吃的'%i)
        c1.send(i)
        c2.send(i)
        time.sleep(1)
    c1.close()
    c2.close()

producter('lkh')

Consumer属于生成器,首先调用next启动生成器,然后通过c.send(n)切换到consumer执行;consumer通过yield拿到消息进行处理,producter继续生产下一条消息。最后通过close()关闭consumer。

整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

 

执行结果:

但是,协程有一个标准定义:

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

基于上面这4点定义,我们刚才用yield实现的程并不能算是合格的

所以在python中,协程已经封装好了。

在python中,greenlet是封装好了的协程,自带直接用,但必须手动切换执行顺序

from greenlet import greenlet

def f1():
    print(12)
    gr2.switch()
    print(12)

def f2():
    print(21)
    gr1.switch()
    print(22)

gr1 = greenlet(f1)
gr2 = greenlet(f2)

gr1.switch()

 gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

自动io切换

import gevent

def func1():
    print('11')
    gevent.sleep(2)
    print('12')

def func2():
    print('21')
    gevent.sleep(1)
    print(22)
    gevent.sleep(0.5)
    print(23)

def func3():
    print(31)
    gevent.sleep(0.5)
    print(32)

#输出11,21,31,32,22,23,12
gevent.joinall([
    gevent.spawn(func1),
    gevent.spawn(func2),
    gevent.spawn(func3),
])

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值