python协程入门介绍

        协程,英文名Coroutines,全称协同程序,协程无法由操作系统来实现,因为操作系统只能调度到线程,协程是比线程还小的单位。

        因此协程只能依靠程序员来实现,程序员写完程序,然后再大脑里大致模拟出程序代码的运行顺序,在能够使用协程的地方加上协程,从而提升程序运行效率。

        但是,不管三七二十一,我们都可以加上协程,即使效率提升并不高。

        当出现IO阻塞时,CPU一直等待IO返回,处于空转状态。这时候用协程,可以执行其他任务。当IO返回结果后,再回来处理数据。充分利用了IO等待的时间,提高了效率。

        又假设工作时,你写了一个程序,这个程序跑出结果需要5分钟,你明明可以在这5分钟内进行摸鱼,但是你的老板却让你这5分钟去帮他拿快递。这就是协程,在CPU没有进行计算而在等待数据交换时都得去工作计算其他程序的指令。

目录

一,协程的核心

二,协程与多线程的比较

三,协程的优点

四,协程的缺点

五,IO密集型和CPU密集型的概念

六,python实现协程

1,yield实现协程

2,asyncio实现协程(重点)


一,协程的核心

        协程的核心是:控制流的让出与恢复

        1,每个协程有自己的执行栈,可以保存自己的执行现场

        2,可以由用户程序按需创建协程(比如:遇到io操作)

        3,协程主动让出(yield)执行权的时候,会保存执行现场(保存中断时的寄存器上下文和栈),然后切换到其他协程。

        4,协程恢复执行(resume)时,根据之前保存的执行现场恢复到中断前的状态,继续执行,这样就通过协程实现了轻量的由用户态调度的多任务模型。

二,协程与多线程的比较

        请看下图:

        1,在单线程同步模型中,任务按照顺序执行。如果某个任务因为I/O而阻塞,其他所有的任务都必须等待,直到它完成之后它们才能依次执行。

        2,多线程版本中,这3个任务分别在独立的线程中执行。这些线程由操作系统来管理,在多处理器系统上可以并行处理,或者在单处理器系统上交错执行。这使得当某个线程阻塞在某个资源的同时其他线程得以继续执行。

        3,协程版本的程序中,3个任务交错执行,但仍然在一个单独的线程控制中。当处理I/O或者其他昂贵的操作时,注册一个回调到事件循环中,然后当I/O操作完成时继续执行。回调描述了该如何处理某个事件。事件循环轮询所有的事件,当事件到来时将它们分配给等待处理事件的回调函数。 

        总结:协程问题的解决关键在于上下文切换,协程的上下文的关键数据似乎存在CPU的寄存器。

三,协程的优点

        1,由于自身带有上下文和栈,无需线程上下文切换的开销,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级;

        2,原子操作的锁定及同步的开销;

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

        4,单线程内就可以实现并发的效果,最大限度地利用cpu,且可扩展性高,成本低(注:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理)

四,协程的缺点

        1,无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上。

        2,当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。

五,IO密集型和CPU密集型的概念

        1,一个计算为主的应用程序(CPU密集型程序),多线程或多进程跑的时候,可以充分利用起所有的 CPU 核心数,比如说16核的CPU ,开16个线程的时候,可以同时跑16个线程的运算任务,此时是最大效率。但是如果线程数/进程数远远超出 CPU 核心数量,反而会使得任务效率下降,因为频繁的切换线程或进程也是要消耗时间的。因此对于 CPU 密集型的任务来说,线程数/进程数等于 CPU 数是最好的了。

        2,如果是一个磁盘或网络为主的应用程序(IO密集型程序),一个线程处在 IO 等待的时候,另一个线程还可以在 CPU 里面跑,有时候 CPU 闲着没事干,所有的线程都在等着 IO,这时候他们就是同时的了,而单线程的话,此时还是在一个一个等待的。我们都知道IO的速度比起 CPU 来是很慢的。此时线程数可以是CPU核心数的数倍(视情况而定)。

        总结:任务管理器里占用CPU很高的就是CPU密集型程序。打开一个软件,鼠标光标一直转圈卡死的就是IO密集型,微微卡死是因为没有CPU为程序进行处理。

六,python实现协程

        asyncio协程是写爬虫比较好的方式。比多线程和多进程都好,开辟新的线程和进程是非常耗时的操作。

1,yield实现协程

        我们知道,一个函数中只要写了yield,则他就是一个生成器。程序会在yield处挂起,直到next()方法将其唤醒。

import time


def func1():
    for i in range(3):
        print(f'北京:第{i}次打印啦')
        yield  # 只要方法包含了yield,就变成一个生成器
        time.sleep(1)


def func2():
    g = func1()  # func1是一个生成器,func1()就不会直接调用,需要通过next()
    print(type(g))
    for k in range(3):
        print(f'上海:第{k}次打印了')
        next(g)  # 继续执行func1的代码
        time.sleep(1)


if __name__ == '__main__':
    # 有了yield,我们实现了两个任务的切换+保存状态
    start_time = time.time()
    func2()
    end_time = time.time()
    print(f"耗时{end_time - start_time}")  # 耗时5.0秒,效率差别不大

        基于yield并发执行,多任务之间来回切换,这就是个简单的协程的体现,但是他能够节省I/O时间吗?答案是:不能。

2,asyncio实现协程(重点)

        1,正常的函数执行时是不会中断的,所以你要写一个能够中断的函数,就需要加 async,async的意思是异步。

        2,async 是一个修饰符,用来声明一个函数为异步函数,异步函数的特点是能在函数执行过程中挂起,去执行其他,异步函数,等到挂起条件(假设挂起条件是 sleep(5) )消失后,也就是5秒到了再回来执行。

        3,await()方法用来声明程序挂起,比如异步程序执行到某一步时需要等待的时间很长,就将此挂起,去执行其他的异步程序。

        4,asyncio模块是python3.5之后的协程模块,是python实现并发重要的包,这个包使用事件循环驱动实现并发。

        5,asyncio实现的是异步IO。也就是遇到IO阻塞时,不会干等待,而是跑去计算其他指定的指令。所谓IO阻塞,并不是说运行停止了,而是说IO过程费时间而已,IO过程CPU并不参与,而是一些其他硬件在忙活,比如硬盘和网卡向内存写数据。CPU需要等待这些数据放在内存之后才能操作。

        下面介绍一个最简单的一部IO代码:

import asyncio
import time


async def func1(sec):  # async表示方法是异步的,在函数变量名之前使用async修饰,说明该函数执行过程中可以被挂起,是一个协程对象而不是普通函数
    for i in range(sec):
        print(f'北京:第{i}次打印啦')
        await asyncio.sleep(1)  # await说明程序挂起,asyncio.sleep(1)是结束挂起的条件,也就是1s结束挂起,函数从挂起处继续执行,
        # 在这1s内,自动去执行其他协程,是协程而不是普通函数
    return "func1执行完毕"


async def func2():
    for k in range(3):
        print(f'上海:第{k}次打印了')
        await asyncio.sleep(1)
    return "func2执行完毕"


async def main():
    res = await asyncio.gather(func1(3), func2())  # asyncio.gather()方法,Return a future aggregating results from the given coroutines/futures.

    # await异步执行func1方法
    # 返回值为函数的返回值列表
    print(res)


if __name__ == '__main__':
    start_time = time.time()
    asyncio.run(main())
    end_time = time.time()
    print(f"耗时{end_time - start_time}")  # 耗时3秒,效率极大提高
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值