python中的协程基本介绍以及简单使用,协程是什么?

1 篇文章 0 订阅
1 篇文章 0 订阅

先理解什么是协程:
1,协程又称微线程,它的上下文切换不是由cpu进行控制。
2,一个线程中可以包含多个协程,对cpu而言,并不存在协程这个概念。
3,通俗来说,协程就是协同多任务。
4,协程拥有自己的寄存器上下文和栈,协程调度切换到其他协程时,将寄存器上下文和栈保存,在切回到当前协程的时候,恢复先前保存的寄存器上下文和栈。

协程有什么优点?
1,无需负担上下文切换的开销。
2,不需要加锁。
3,程序员切换控制流,方便编程。
4,高并发、高扩展、低成本(一个CPU支持上万的协程都不是问题)。

协程有什么缺点?
1,协程自己无法利用CPU多核资源(除非与多进程或者多线程配合);
2,遇到阻塞操作会使整个程序阻塞。

python中的生成器关键字yield其实就可以简单的模拟实现协程的功能

import time
def func1():
    print('任务1开始')
    tem = 0
    for i in range(10):
        print('yield上方的tem:',tem)
        print('任务1暂停')
        print('当前i值:',i)
        tem = yield i  #生成i值,切换到func2函数
        print('任务1继续')
        print('yield下方的tem:',tem)  #当i为偶数时,这时候的send还未触发,tem = yield i 的返回值为None
        time.sleep(1)

def func2():
    print('任务2开始')

    for i in range(6):
        print('任务二执行')
        print(next(func))  #切换到func1函数执行
        func.send(i+10)  #给右边的tem赋值

if __name__ == '__main__':
    func = func1()
    func2()

结果

任务2开始
任务二执行
任务1开始
yield上方的tem: 0
任务1暂停
当前i值: 0
0
任务1继续
yield下方的tem: 10
yield上方的tem: 10
任务1暂停
当前i值: 1
任务二执行
任务1继续
yield下方的tem: None
yield上方的tem: None
任务1暂停
当前i值: 2
2
任务1继续
yield下方的tem: 11
yield上方的tem: 11
任务1暂停
当前i值: 3
任务二执行
任务1继续
yield下方的tem: None
yield上方的tem: None
任务1暂停
当前i值: 4
4
任务1继续
yield下方的tem: 12
yield上方的tem: 12
任务1暂停
当前i值: 5
任务二执行
任务1继续
yield下方的tem: None
yield上方的tem: None
任务1暂停
当前i值: 6
6
任务1继续
yield下方的tem: 13
yield上方的tem: 13
任务1暂停
当前i值: 7
任务二执行
任务1继续
yield下方的tem: None
yield上方的tem: None
任务1暂停
当前i值: 8
8
任务1继续
yield下方的tem: 14
yield上方的tem: 14
任务1暂停
当前i值: 9
任务二执行
任务1继续
yield下方的tem: None
Traceback (most recent call last):
  File "C:/Users/Administrator/PycharmProjects/review/python知识复习/02/gevent协程.py", line 27, in <module>
    func2()
  File "C:/Users/Administrator/PycharmProjects/review/python知识复习/02/gevent协程.py", line 22, in func2
    print(next(func))
StopIteration

可以看到,这个yield切换是有缺陷的,中间会跳过一次生成出来的i值。
最后的报错是我故意的,生成器取不到值了就会报StopIteration的错误,你把func2中的range(6)改成5及以下就行了。

这样模拟协程的使用有点笨重和傻傻的,其实关于协程python有很多已经封装好的第三方模块可以使用

greenlet
greenlet是用c语言写的用来实现协程的第三方python模块,它可以实现多任务之间的随意切换,并不需要像上方的案例一样,先把函数变成一个生成器

import greenlet

def func1():
    for i in range(6):
        print(i)
        g2.switch()  #切换到func2


def func2():
    for i in range(6,12):
        print(i)
        g1.switch()  #切换到func1


g1 = greenlet.greenlet(func1)
g2 = greenlet.greenlet(func2)
g1.switch()

结果

0
6
1
7
2
8
3
9
4
10
5
11

上面这段代码有一个缺点,就是任务的切换都是我们手动调用的,比如你在switch前面加一个time.sleep(),它就会等待sleep完再往下执行,并不会自动切换
那么如何让任务遇到I/O操作就切换?

import gevent

def func1():
    print('任务1')
    gevent.sleep(2)  #遇到等待操作切换到func2,gevent.sleep是gevent封装的非阻塞模块
    print('任务1go')

def func2():
    print('任务2')
    gevent.sleep(2)  #这里又切换回func1
    print("任务2go")

start = time.time()
gevent.joinall([gevent.spawn(func1),
                gevent.spawn(func2)])
print(time.time()-start)

结果

任务1
任务2
任务1go
任务2go
2.018115520477295

如果我改一下代码:

import gevent

def func1():
    print('任务1')
    time.sleep(2)
    print('任务1go')

def func2():
    print('任务2')
    time.sleep(2)
    print("任务2go")

start = time.time()
gevent.joinall([gevent.spawn(func1),
                gevent.spawn(func2)])
print(time.time()-start)

结果

任务1
任务1go
任务2
任务2go
4.018229961395264

现在就没有进行异步调用了,也就是说,遇到了IO操作,并没有实现任务切换的效果了,这是为什么呢?在python的标准库的许多模块当中,都是阻塞式系统调用,time.sleep()是系统模块,gevent.sleep()是gevent封装好的非阻塞时间模块,所以用gevent.sleep()的时候协程就会自动进行任务切换。
那么系统模块的功能这么丰富,就想用系统模块怎么办呢?
Monkey-patching猴子补丁就出来了,用了猴子补丁,这些
socket、ssl、threading和 select、time、multiprocessing等模块都会被修改成支持gevent功能的模块,然后就可以愉快的使用了

import gevent
from gevent import monkey
monkey.patch_all()

def func1():
    print('任务1')
    time.sleep(2)
    print('任务1go')

def func2():
    print('任务2')
    time.sleep(2)
    print("任务2go")

start = time.time()
gevent.joinall([gevent.spawn(func1),
                gevent.spawn(func2)])
print(time.time()-start)

结果

任务1
任务2
任务1go
任务2go
2.012115001678467

注意,猴子补丁尽量早点打,否则可能出现一些奇怪的错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值