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

先理解什么是协程: 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

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、付费专栏及课程。

余额充值