协程(Coroutines),也叫纤程(Fiber)。全称协同程序,用来实现任务协作。是一种在线程中,比线程更轻量化的存在,由程序员自己写程序来管理。当出现IO阻塞时,CPU一直等待IO返回,处于空转状态。这时候用协程,可以执行其他任务。当IO返回结果后,再回来处理数据。充分利用了IO等待时间,提高了效率。

协程的核心(控制流的让出和恢复)

  • 每个协程有自己的执行栈,可以保存自己的执行现场
  • 可以由用户程序按需创建协程
  • 协程主动让出(yield)执行权时,会保存执行现场(保存中断时的寄存器上下文和栈),然后切换到其他协程
  • 协程恢复执行(resume)时,根据之前保存的执行现场恢复到中断前的状态,继续执行,这样就通过协程实现了轻量的由用户调度的多任务模型

协程和多线程比较

有三个任务要完成,每个任务都在等待I/O操作时阻塞自身。阻塞在I/O操作上花的时间是灰色部分

  1.  在单线程同步模型中,任务按照顺序执行。如果某个任务因为I/O阻塞,其他所有的任务都必须等待,直到它完成之后它们才能依次执行。
  2. 多线程版本中,这3个任务分别在独立的线程中执行。这些线程由操作系统来管理,在多处理器系统上可以并行处理,或者在单处理器上交错执行。这使得当某个线程阻塞在某个资源的同时,其他线程可以继续执行
  3. 协程版本的程序中,3个任务交错执行,但仍然在一个单独的线程控制中。当处理I/O或者其他昂贵的操作时,注册一个回调到时间循环中,然后当I/O操作完成时继续执行。回调描述了该如何处理某个事件。事件循环轮询所有的事件,当事件到来时将它们分配给等待处理事件的回调函数。

协程的优点

  1. 由于自生带有上下文和栈,无需线程上下文切换的开销,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
  2. 无需原子操作的锁定及同步的开销
  3. 方便切换控制流,简化编程模型
  4. 单线程内就可以实现并发的效果,最大限度地利用CPU,且可扩展性高,低成本。

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

协程的缺点

  1. 无法利用多核资源:协程的本质是个单线程,它不能同时将单个CPU的多个核用上,协程需要和进程配合才能运行在多个CPU上。
  2. 当让我们日常所编写的绝大部分应用都没有这个必要,除非是CPU密集型引用。

asyncio实现协程

  1. 正常的函数执行时是不会中断的,所以你要写一个能够中断的函数,就需要加async
  2. async用来声明一个函数为异步函数,异步函数的特点就是能在函数执行过程中挂起,去执行其他异步函数,等到挂起条件消失后再回来执行。
  3. await用来声明程序挂起,比如异步程序执行到某一步时需要等待的时间很长,就将此挂起,去执行其他的异步程序
  4. asyncio是Python3.5之后的协程模块,是Python实现并发重要的包,这个包使用时间虚幻驱动实现并发
#encoding=utf-8
# 不适用asyncio的任务切换
import time
def func1():
    for i in range(3):
        print(f"原神启动{i}次")
        time.sleep(1)
    return "func1执行完毕"

def func2():
    for i in range(3):
        print(f"老头环启动{i}次")
        time.sleep(1)
    return "func2执行完毕"

def main():
    func1()
    func2()

if __name__ == '__main__':
    start_time = time.time()
    main()
    end_time = time.time()
    print(f"耗时{end_time-start_time}")

"""
原神启动0次
原神启动1次
原神启动2次
老头环启动0次
老头环启动1次
老头环启动2次
耗时6.057191848754883

Process finished with exit code 0
"""
# encoding=utf-8
# asyncio异步IO的典型使用方式
import asyncio
import time

async def func1():
    for i in range(3):
        print(f"原神启动{i}次")
        await asyncio.sleep(1)
    return "func1执行完毕"

async def func2():
    for i in range(3):
        print(f"老头环启动{i}次")
        await asyncio.sleep(1)
    return "func2执行完毕"

async def main():
    res = await asyncio.gather(func1(),func2())
    print(res)

if __name__ == '__main__':
    start_time = time.time()
    asyncio.run(main())
    end_time = time.time()
    print(f"耗时{end_time-start_time}")

"""
原神启动0次
老头环启动0次
原神启动1次
老头环启动1次
原神启动2次
老头环启动2次
['func1执行完毕', 'func2执行完毕']
耗时3.0223679542541504

Process finished with exit code 0
"""

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐