协程的用法

协程基础知识

概念

协程也可以被称为微线程,是一种用户态内的上下文切换技术。简而言之,就是通过一个线程实现代码块相互切换。

协程与线程的不同

协程不是计算机提供的,而是程序员人为创造的。线程和进程是由计算机操作系统提供的。

协程与多线程的区别:

  • 多线程运行时:保证只有一个线程在运行,其他线程等待调度。这是python GIL锁导致的,会浪费一些资源开销。

  • 协程运行时:在一个线程中运行多个任务,任务与任务之间来回切换,并在同一时间内只能运行一个任务。节省资源开销。

协程实现

协程实现有四种方法:

  • greenlet:早期第三方模块,用于实现协程代码(gevent协程就是基于greenlet实现)

  • yield:生成器,借助生成器的特点也可以实现协程代码。

  • asyncio装饰器:在Python3.4中引入的模块用于编写协程代码。

  • async & awiat:在Python3.5中引入的两个关键字,结合asyncio模块可以更方便的编写协程代码。

第一种:greentlet

使用greentlet来实现协程并发,是早期的实现方式。

from greenlet import greenlet


def study():
    print(1)
    # 3.切到g2函数中
    g2.switch()
    print(2)
    # 3.切到g2函数中
    g2.switch()


def eat():
    print(3)
    # 3.切到g1函数中
    g1.switch()
    print(4)


# 1.写入需要并发的函数名
g1 = greenlet(study)
g2 = greenlet(eat)
# 2.开启程序
g1.switch()

'''
弊端:需要自己手动去切
运行结果:
1
3
2
4
'''

gevent是对greentlet的进一步封装,实现了遇到阻塞自动切换。

import gevent

def funa(n):
    for i in range(n):
        print(gevent.getcurrent(),i)   # 返回当前正在执行的greenlet协程
        gevent.sleep(1)     # 遇到io阻塞,自动切换

# 1.写入需要并发的函数名
g1 = gevent.spawn(funa,2)
g2 = gevent.spawn(funa,2)
g3 = gevent.spawn(funa,2)

# 2.开启程序
g1.join()
g2.join()
g3.join()

'''
运行结果:
<Greenlet at 0x2c0544017b0: funa(2)> 0
<Greenlet at 0x2c07580c590: funa(2)> 0
<Greenlet at 0x2c07580c7b0: funa(2)> 0
<Greenlet at 0x2c0544017b0: funa(2)> 1
<Greenlet at 0x2c07580c590: funa(2)> 1
<Greenlet at 0x2c07580c7b0: funa(2)> 1
'''

gevent当遇到gevent.sleep(1)才会识别为阻塞,遇到time.sleep(1)并不会识别为阻塞。

如果想让time模块能被识别为io阻塞,就需要打补丁。

import gevent
import time
from gevent import monkey
monkey.patch_all()   # 打补丁

def task(name):
    for i in range(3):
        print(f'我的名字是{name},当前任务号{i}')
        time.sleep(1)   # 使时间模块能被gevent识别为io阻塞----实现自动切换

gevent.joinall([
    gevent.spawn(task,'Wilia'),
    gevent.spawn(task,'维佳')
])
'''
运行结果:
我的名字是Wilia,当前任务号0
我的名字是维佳,当前任务号0
我的名字是Wilia,当前任务号1
我的名字是维佳,当前任务号1
我的名字是Wilia,当前任务号2
我的名字是维佳,当前任务号2
'''

第二种:yield(了解)

基于Python生成器的yield和yield form关键字实现协程代码。

def func1():
    yield 1
    yield from func2()
    yield 2


def func2():
    yield 3
    # yield from func1()
    yield 4


f1 = func1()
for item in f1:
    print(item)
'''
弊端:1.函数一切换到函数二之后不能手动的在函数二中切换到函数一 2.不能实现自动切换
运行结果:
1
3
4
2
'''

第三种:asyncio装饰器

遇到IO阻塞自动切换

import asyncio


@asyncio.coroutine
def func1():
    print(1)
    # 网络IO请求:下载一张图片
    yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(2)


@asyncio.coroutine
def func2():
    print(3)
    # 网络IO请求:下载一张图片
    yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(4)


'''
将协程对象交给future,将当前函数对象转为一个task对象。
如果asyncio遇到了task对象 则将task对象作为并发进行执行。
如果想用协程完成并发,则必须将普通协程对象转为一个task对象,
因为协程对象默认是同步执行,转为task对象才是异步执行。
'''
tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]


# 创建了一个容器(用来存放task任务对象) 事件循环
'''
事件循环是协程的一个重要的组成部分,将需要运行的任务由事件循环调度。
事件循环只能运行 --> task(任务)对象、asyncio对象、协程对象
因此这里要把列表转换为asyncio对象
'''
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
'''
弊端:当前的任务切换不是切换到某一个具体的任务
厉害之处:遇到IO阻塞自动切换
运行结果:
1
3
2
4
'''

第四种:async & awiat(主流)

import asyncio


# 创建协程函数
async def func1():
    print(1)
    # 网络IO请求:下载一张图片
    await asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(2)


# 创建协程函数
async def func2():
    print(3)
    # 网络IO请求:下载一张图片
    await asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(4)


'''
1.如果一个函数被async声明,则当前函数是一个协程函数对象
2.协程函数对象不能被直接调用
3.如果想要去运行协程函数,我们需要借助事件循环去运行当前的协程函数
4.await 是协程中的一个关键字 作用是:等待一些耗时任务并拿到这些任务的返回值后才会解堵塞
5.事件循环只能执行可以被等待的对象:协程对象、asyncio对象、task对象
'''

tasks = [
    # 将协程对象转为一个task对象用来进行并发执行
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]

# 创建完task对象之后需要借助事件循环去运行task对象
loop = asyncio.get_event_loop()
# asyncio.wait():把不同对象转换为asyncio对象
loop.run_until_complete(asyncio.wait(tasks))
'''
运行结果:
1
3
2
4
'''

进程,线程,协程总结

从资源消耗来看

切换需要的资源最大的:进程

切换需要的资源一般的:线程

切换需要的资源最小的:协程

从使用场景来看

多进程:CPU密集型操作(如计算密集型)

多线程:IO密集型(读写数据操作比较多的),如涉及到网络、磁盘IO的任务都是IO密集型

协程:IO阻塞且需要大量并发的场景

一般来说当遇到CPU+IO密集型时常采用:多进程+协程

从资源竞争来看

进程之间不共享全局变量,线程、协程之间共享全局变量,但要注意资源竞争的问题

联系

一个进程可以拥有多个线程

一个线程可以拥有多个协程

多线程、多进程都是同步机制,而协程则是异步机制

下篇文章我们来一起探讨一下什么是异步。

(剧透:异步是现象,协程是实现异步现象的方式)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值