python之协程

1、协程

1.1、定义
协程是为了实现单线程下实现并发,属于线程下
协程要解决的问题: 保存状态+切换
    
协程并不是真实存在的某个东西,而是程序员想象出来的
程序员控制,不让自己的程序遇到io,看上去,就实现并发了

优点如下:
协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
单线程内就可以实现并发的效果,最大限度地利用cpu

缺点如下:
协程的本质是单线程下无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

总结协程特点:
必须在只有一个单线程里实现并发
修改共享数据不需加锁
用户程序里自己保存多个控制流的上下文栈(需要保存状态)
附加: 一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))
1.2、yield保存状态+切换
切换关键的一点是: 保存状态(从原来停留的地方继续切)
return: 只能执行一次,结束函数的标志
yield: 生成器,只要函数中有yield关键字,这个函数就是生成器,通过yield可以实现保存状态+切换
生成器本质就是一个迭代器,那么迭代器怎么用呢?用一个next()方法
 
1yield语句的形式: yield 1
yield功能1: 可以用来返回值,可以返回多次值
yield功能2: 可以把函数暂停住,保存原来的状态
 
2yield表达式的形式: x = yield send可以把一个函数的结果传给另一个函数,以此实现单线程内程序之间的切换
send()要想用就得先next()一下
但是要用send至少要用两个yield 
1.3、串行与yield执行时间对比
# 串行执行
import time

def func1():
    for i in range(100000000):
        i += 1

def func2():
    for i in range(100000000):
        i += 1

if __name__ == '__main__':
    start_time = time.time()
    func1()
    func2()
    print(time.time() - start_time)  # 9.42405915260315

    
# 通过yield,实现保存状态加切换(自定义的切换,并不是遇到io才切,所以它并不能节约时间)
# 单纯的切换,不但不会提高效率,反而会降低效率    
import time

def func1():
    for i in range(100000000):
        i += 1
        yield

def func2():
    g = func1()  # 先执行一下func1
    for i in range(100000000):
        i += 1
        next(g)  # 回到func1执行

if __name__ == '__main__':
    start_time = time.time()
    func2()
    print(time.time() - start_time)  # 22.581471920013428
1.4、greenlet
greenlet模块和yield没有什么区别,就只是单纯的切,跟效率无关
只不过比yield更好一点,切的时候方便一点。但是仍然没有解决效率
greenlet可以让你在多个任务之间来回的切
安装:
    pip3 install greenlet

from greenlet import greenlet
import time

# 遇到io不会切,初级模块,gevent模块基于它写的,处理io切换
def eat(name):
    print('{}吃了一口饭'.format(name))
    time.sleep(1)  # 当遇到IO的时候它也没有切,这就得用gevent了
    p.switch('张三')
    print('{}又吃了一口饭'.format(name))
    p.switch()

def play(name):
    print('{}玩了一会儿'.format(name))
    e.switch()
    print('{}又玩了一会儿'.format(name))
    e.switch()

if __name__ == '__main__':
    e = greenlet(eat)
    p = greenlet(play)
    e.switch('张三')  # 可以在第一次switch时传入参数,以后都不需要
1.5、gevent
gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是greenlet,
它是以C扩展模块形式接入python的轻量级协程。 greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

import gevent
import time

def eat(name):
    print('{}吃了一口饭'.format(name))
    gevent.sleep(1)  # io操作
    print('{}又吃了一口饭'.format(name))

def play(name):
    print('{}玩了一会儿'.format(name))
    gevent.sleep(2)
    print('{}又玩了一会儿'.format(name))

if __name__ == '__main__':
    # 方式一:
    start_time = time.time()
    e = gevent.spawn(eat, 'allen')
    p = gevent.spawn(play, 'allen')
    e.join()  # 等待e执行完成
    p.join()
    print('主')
    print(time.time() - start_time)  # 2.0141146183013916

    # 方式二:
    start_time = time.time()
    eat('allen')
    play('allen')
    print(time.time() - start_time)  # 3.012643337249756
1.6、利用猴子补丁实现gevent
# 利用猴子补丁
import gevent
import time
from gevent import monkey;monkey.patch_all()

def eat(name):
    print('{}吃了一口饭'.format(name))
    time.sleep(1)  # io操作,被猴子补丁替换之后会执行gevent.sleep()
    print('{}又吃了一口饭'.format(name))

def play(name):
    print('{}玩了一会儿'.format(name))
    time.sleep(2)
    print('{}又玩了一会儿'.format(name))

if __name__ == '__main__':
    start_time = time.time()
    e = gevent.spawn(eat, 'allen')
    p = gevent.spawn(play, 'allen')
    e.join()
    p.join()
    print('主')
    print(time.time() - start_time)  # 2.01104736328125
1.7、asyncio
1.7.1、方式一(python3.5以前版本)
import asyncio
import time

# 把普通函数变成协程函数
# 3.5以前这么写
@asyncio.coroutine
def task1():
    print('开始了')
    yield from asyncio.sleep(2)  # asyncio.sleep(2) 模拟io
    print('结束了')

@asyncio.coroutine
def task2():
    print('开始了')
    yield from asyncio.sleep(1)
    print('结束了')

loop = asyncio.get_event_loop()  # 获取一个事件循环对象

# 协程函数加括号,并不会真正的去执行,它需要提交给loop,让loop循环着去执行
# 协程函数列表
start_time = time.time()
t = [task1(), task2()]
loop.run_until_complete(asyncio.wait(t))
loop.close()
print(time.time() - start_time)  # 2.0018436908721924
1.7.2、方式二
import asyncio
import time
from threading import current_thread

# 表示是协程函数,等同于3.5之前的装饰器
async def task1():
    print('开始了')
    print(current_thread().name)  # MainThread
    await asyncio.sleep(2)  # await等同于原来的yield from
    print('结束了')

async def task2():
    print('开始了')
    print(current_thread().name)  # MainThread
    await asyncio.sleep(1)
    print('结束了')

loop = asyncio.get_event_loop()

start_time = time.time()
t = [task1(), task2()]
loop.run_until_complete(asyncio.wait(t))
loop.close()
print(time.time() - start_time)  # 2.0022599697113037
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值