Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python 协程 #169

Open
Qingquan-Li opened this issue Apr 27, 2021 · 0 comments
Open

Python 协程 #169

Qingquan-Li opened this issue Apr 27, 2021 · 0 comments
Labels

Comments

@Qingquan-Li
Copy link
Owner

Qingquan-Li commented Apr 27, 2021

参考:


一、协程简介

Coroutine 协程子例程的更一般形式。 子例程可以在某一点进入并在另一点退出。 协程则可以在许多不同的点上进入、退出和恢复。

协程,又称微线程,纤程。一句话说明什么是线程:协程是一种用户态的轻量级线程。

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。

协程使用发展史:

随着互联网的快速发展,你逐渐遇到了 C10K 瓶颈,也就是同时连接到服务器的客户达到了一万个。于是很多代码跑崩了,进程上下文切换占用了大量的资源,线程也顶不住如此巨大的压力,这时, NGINX 带着事件循环出来拯救世界了(在高并发下能保持低资源低消耗高性能):
事件循环启动一个统一的调度器,让调度器来决定一个时刻去运行哪个任务,于是省却了多线程中启动线程、管理线程、同步锁等各种开销。

再到后来,出现了一个很有名的名词,叫做回调地狱 callback hell(JavaScript),这种工具完美地继承了事件循环的优越性,同时还能提供 async / await 语法糖,解决了执行性和可读性共存的难题。于是,协程逐渐被更多人发现并看好,也有越来越多的人尝试用 Node.js (JavaScript 是一门单线程的语言,无法多线程实现并发,但可以使用协程实现并发)做起了后端开发。

使用生成器,是 Python 2 开头的时代实现协程的老方法了,Python 3.7 提供了新的基于 asyncio 和 async / await 的方法


进程、线程、协程 的上下文切换比较

进程process 线程thread 协程coroutine
切换者 操作系统(进程是资源分配的最小单位) 操作系统(线程是CPU调度的最小单位) 用户(编程者/应用程序)
切换时机 根据操作系统自己的切换策略,用户不感知 根据操作系统自己的切换策略,用户不感知 用户自己(的程序)决定
切换内容 页面全局目录
内核栈
硬件上下文
内核栈
硬件上下文
硬件上下文
切换内容的保存位置 内核栈 内核栈 用户自己的变量(用户栈或堆)
切换过程 用户态-内核态-用户态 用户态-内核态-用户态 用户态(没有陷入内核态)
切换效率

协程总结:

  • 协程和多线程的区别,主要在于两点,一是协程为单线程;二是协程由用户决定,在哪些地方交出控制权,切换到下一个任务。
  • 协程的写法更加简洁清晰,把 async / await 语法和 create_task 结合来用,足以应对中小级别的并发需求。
  • 写协程程序的时候,脑海中要有清晰的事件循环概念,知道程序在什么时候需要(await 以执行下一个事件循环任务)暂停、等待 I/O,什么时候需要一并执行到底。

进程、线程、协程应用场景

  • 协程的主要应用场景是 IO 密集型任务,总结几个常见的使用场景:
  • IO密集型任务(例如网络调用):线程或协程
  • CPU密集型任务:使用多个进程,绕开 GIL 的限制,利用所有可用的CPU核心,提高效率。
  • 大并发下的最佳实践:多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。



二、协程示例

示例1:同步(不使用协程)爬取4个URL

import time
import timeit


def crawl_page(url):
    print('crawling {}'.format(url))
    # 休眠时间取决于 url 最后的那个数字
    sleep_time = int(url.split('_')[-1])
    time.sleep(sleep_time)
    print('OK {}'.format(url))


def main(urls):
    for url in urls:
        crawl_page(url)


if __name__ == "__main__":
    start = timeit.default_timer()
    main(['url_1', 'url_2', 'url_3', 'url_4'])
    stop = timeit.default_timer()
    print('Time:', stop - start)
$ python sync_crawl
# 输出 crawling url_num 后,等待 num 秒后输出对应的 OK url_num
crawling url_1
OK url_1
crawling url_2
OK url_2
crawling url_3
OK url_3
crawling url_4
OK url_4
Time: 10.012153034

示例2:异步(使用协程)爬取4个URL

import timeit
import asyncio


async def crawl_page(url):
    print('crawling {}'.format(url))
    sleep_time = int(url.split('_')[-1])
    await asyncio.sleep(sleep_time)
    print('OK {}'.format(url))


async def main(urls):
    # for url in urls:
    #     await crawl_page(url)
    # await 是同步调用,因此,crawl_page(url) 在当前的调用结束之前,
    # 是不会触发下一次调用的。这里相当于用异步接口写了个同步代码。
    # docs.python.org/zh-cn/3.8/tutorial/datastructures.html#list-comprehensions
    tasks = [asyncio.create_task(crawl_page(url)) for url in urls]
    for task in tasks:
        await task
    # 以上for循环也可写作如下。其中,*tasks 解包列表,将列表变成了函数的参数;
    # 与之对应的是, ** dict 将字典变成了函数的参数。
    # await asyncio.gather(*tasks)


start = timeit.default_timer()
# asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))
asyncio.run(main(['url_4', 'url_3', 'url_2', 'url_1']))
stop = timeit.default_timer()
print('Time:', stop - start)
$ python async_crawl
# 立刻输出4个 crawling url_num 后,第1秒后输出 OK url_1,第2秒后输出 OK url_2 ... 第4秒后输出 OK url_4
crawling url_4
crawling url_3
crawling url_2
crawling url_1
OK url_1
OK url_2
OK url_3
OK url_4
Time: 4.00415018
@Qingquan-Li Qingquan-Li changed the title 协程的简单使用(爬虫示例) Python协程的简单使用(爬虫示例) Apr 27, 2021
@Qingquan-Li Qingquan-Li changed the title Python协程的简单使用(爬虫示例) Python协程的简单使用 Apr 27, 2021
@Qingquan-Li Qingquan-Li changed the title Python协程的简单使用 Python 协程 Apr 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant