万字长文,带你入门异步编程

你好,我是悦创。

公众号:AI悦创,抢先阅读文章!

博客原文:https://www.aiyc.top/archives/346.html

异步模型是事件驱动模型的基础,而事件驱动的编程很多,比如:VB、PyQt。

事件驱动是指在持续事务管理过程中,进行决策的一种策略,即跟随当前时间点上出现的事件,调动可用资源,执行相关任务,使不断出现的问题得以解决,防止事务堆积。在计算机编程、公共关系、经济活动等领域均有应用。

所谓事件驱动,简单地说就是你点什么按钮(即产生什么事件),电脑执行什么操作(即调用什么函数)。当然事件不仅限于用户的操作,事件驱动的核心自然是事件。从事件角度说,事件驱动程序的基本结构是由一个事件收集器、一个事件发送器和一个事件处理器组成。事件收集器专门负责收集所有事件,包括来自用户的(如鼠标、键盘事件等)、来自硬件的(如时钟事件等)和来自软件的(如操作系统、应用程序本身等)。事件发送器负责将收集器收集到的事件分发到目标对象中。事件处理器做具体的事件响应工作,它往往要到实现阶段才完全确定,因而需要运用虚函数机制(函数名往往取为类似于HandleMsg的一个名字)。对于框架的使用者来说,他们唯一能够看到的是事件处理器。这也是他们所关心的内容。

视图(即我们通常所说的“窗口”)是“事件驱动”应用程序的另一个要元。它是我们所说的事件发送器的目标对象。视图接受事件并能够对其进行处理。当我们将事件发送到具体的视图时,实际上我们完成了一个根本性的变化:从传统的流线型程序结构到事件触发方式的转变。这样应用程序具备相当的柔性,可以应付种种离散的、随机的事件。

换个说法:你点击出来一个页面,你点击一下它给你一个反馈,你点击一下它给你个反馈,这个就是事件驱动。

在这里插入图片描述

图 一

异步活动的执行模型可以只有一个单一的主控制流,能在单核心系统和多核心系统中运行。

在并发执行的异步模型中,许多任务被穿插在同一时间线上,所有任务都由一个控制流执行(单一线程)。任务的执行可能被暂停或恢复,中间的这段时间线程将会去执行其他任务。

比如,上图(图一)就是一个单线程,但是它去可以穿插许多任务(Task 1、Task 2、Task 3)。比方说:Task 1 它需要执行三次,每执行一次 Task 1 就要等待一段时间才可以继续执行。而异步的时候,我们就可以在它(Task 1 )等待的时候,分出去 Task 1 去等待,然后让该线程去执行下一个任务(Task 2)。当 Task 2 也需要等待的时候,也就把 Task 2 放出去等待, 再执行 Task 3 之后循环往复。等哪个准备好了,哪个需要执行就再放回我们的线程上执行。(如果需要等待的化,就再把它剔除出去)

当然,我们这些异步执行任务时随机的,并不是说 Task 1、Task 2、Task 3 按顺序来执行。

异步有一个特点,它的执行顺序时随机的不可控的,一切都是由操作系统随机进行的。(我们只需要定义任务即可)具体哪个时刻执行哪个任务我们时无法预测的。

tips:

  1. 它的所有任务时单线程,同学们不要认为是多线程。
  2. 它的异步就是一条时间线上的,不可控的任务随机执行。
  3. 这个任务可能被暂停或恢复。

Ps:任务需要等待一段时间的时候,就被暂停放出去。等任务等待时间过去要再次执行任务的时候,任务就被恢复。

所以,上面说了这么多,异步其实就是单线程执行多种任务,这些线程上的任务可以被暂停或者恢复,不断地一些小人物穿插在一起。

异步中间其实主要是协程,同学们可能听过这两个概念,不过异步和协程是不一样的,它们是搭配起来用的。

1. 五分钟入门协程

在 Python 3.4 时候引进了协程的概念,它使用一种单线程单进程的的方式实现并发。谈到并发,大多数朋友想到更多的应该是多进程和多线程,它们都是比较消耗系统资源的,今天我们不谈线程和进程,而是来说下当前比较火的协程。

因为在爬虫操作中,协程比多线程更有优势。协程是单线程的,单线程就能实现高并发。

1.1 什么是协程?

协程,英文名是 Coroutine, 又称为微线程,是一种用户态的轻量级线程。协程不像线程和进程那样,需要进行系统内核上的上下文切换,协程的上下文切换是由程序员决定的。在 Python 中协程就是一个可以暂停执行的函数,听起来和生成器的概念一样。

1.2 协程的发展

从 Python3.4 开始协程被加入到标准库,当时的协程是通过 @asyncio.coroutine 和 yeild from 实现的,看起来和生成器的实现方式没什么区别。后来为了更好的区分开协程和生成器,到 Python3.5 的时候引入 async/await 语法糖临时定格了下来,直到 Python3.6 的时候才被更多的人认可,Python3.6 作为 Python3 目前最稳定的版本拥有大量的使用者,后来到了 Python3.7 官方把 async 和 await 作为保留字,同时协程的调用也变得简单了许多,但是,正是保留字的原因导致之前很多 async 为函数名的库报错,典型的有 scrapy,所以这里推荐大家使用 Python3.6。

1.3 协程相对于多线程的优点

多线程编程是比较困难的, 因为调度程序任何时候都能中断线程, 必须记住保留锁, 去保护程序中重要部分, 防止多线程在执行的过程中断。

而协程默认会做好全方位保护, 以防止中断。我们必须显示产出才能让程序的余下部分运行。对协程来说, 无需保留锁, 而在多个线程之间同步操作, 协程自身就会同步, 因为在任意时刻, 只有一个协程运行。总结下大概下面几点:

  • 无需系统内核的上下文切换,减小开销;
  • 无需原子操作锁定及同步的开销,不用担心资源共享的问题;
  • 单线程即可实现高并发,单核 CPU 即便支持上万的协程都不是问题,所以很适合用于高并发处理,尤其是在应用在网络爬虫中。

1.4 协程的定义

使用协程也就意味着你需要一直写异步方法。在 Python 中我们使用 asyncio 模块来实现一个协程。如果我们把 Python 中普通函数称之为同步函数(方法),那么用协程定义的函数我们称之为异步函数(方法)。

注意,以下所有的代码实例运行环境均要求版本大于等于 Python3.6。

1.4.1 同步函数和异步函数的定义

同步函数定义

def regular_double(x):
    return 2 * x

异步函数定义

async def async_double(x):
    return 2 * x

同步函数和异步函数的调用

对于同步函数我们知道是这样调用的:

 regular_double(2)

异步函数如何调用呢?带着这个问题我们看下面的一个简单例子。

import asyncio

async def foo():
    print("这是一个协程")


if __name__ == '__main__':
    loop = asyncio.get_event_loop() # 定义一个事件循环
    try:
        print("开始运行协程")
        coro = foo()
        print("进入事件循环")
        loop.run_until_complete(coro) # 运行协程
    finally:
        print("关闭事件循环")
        loop.close() # 运行完关闭协程

这就是最简单的一个协程的例子,让我们了解一下上面的代码。

首先,需要导入需要的包 -asyncio。然后,创建一个事件循环,因为协程是基于事件循环的。 之后,通过 run_until_complete 方法传入一个异步函数,来运行这个协程。 最后在结束的时候调用 close 方法关闭协程。 综上就是调用一个协程的写法。除此之外协程还有其他的不同之处。

1.4.2 协程之间的链式调用

我们可以通过使用 await 关键字,在一个协程中调用一个协程。 一个协程可以启动另一个协程,从而可以使任务根据工作内容,封装到不同的协程中。我们可以在协程中使用 await 关键字,链式地调度协程,来形成一个协程任务流。像下面的例子一样:

import asyncio


async def main():
    print("主协程")
    print("等待result1协程运行")
    res1 = await result1()
    print("等待result2协程运行")
    res2 = await result2(res1)
    return (res1,res2)


async def result1():
    print("这是result1协程")
    return "result1"


async def result2(arg):
    print("这是result2协程")
    return f"result2接收了一个参数,{arg}"


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    try:
        result = loop.run_until_complete(main())
        print(f"获取返回值:{result}")
    finally:
        print("关闭事件循环")
        loop.close()

输出:

主协程
等待result1协程运行
这是result1协程
等待result2协程运行
这是result2协程
获取返回值:('result1''result2接收了一个参数,result1')
关闭事件循环

在上面,我们知道调用协程需要通过创建一个事件循环然后再去运行。 这里我们需要了解的是如果在协程里想调用一个协程我们需要使用 await 关键字,就拿上面的例子来说在 main 函数里调用协程 result1 和 result2。 那么问题来了:await 干了什么呢?

1.4.3 await 的作用

await 的作用就是等待当前的协程运行结束之后再继续进行下面代码。 因为我们执行 result1 的时间很短,所以在表面上看 result1 和 result2 是一起执行的。这就是 await 的作用。等待一个协程的执行完毕,如果有返回结果,那么就会接收到协程的返回结果,通过使用 return 可以返回协程的一个结果,这个和同步函数的 return 使用方法一样。

这里为了让小白更加理解异步,我再次简洁直白的小结一下:

协程就是一个函数,只是它满足以下几个特征:

  • 依赖 I/O 操作(有 I/O 依赖的操作)
  • 可以在进行 I/O 操作时暂停
  • 无法直接运行

它的作用就是对有大量 I/O 操作的程序进行加速。

Python 协程属于可等待对象,因此可以在其他协程中被等待。上面的 await 是不是听的看着一脸懵?接下来,我再直白一些:

什么叫可等待对象?——await,如果前面被标记 await 就表明他是个协程,我们需要等待它返回一个数据。

import asyncio
async def net():
	return 11
async def main():
	# net() # error
	await net() # right
asyncio.run(main())

import asyncio
async def net():
	return 11
async def main():
	# net() # error
	return await net() # right
print(asyncio.run(main()))

举个例子,我从网络上下载某个数据文件下载到我的本地电脑上,这很显然是一个 I/O 操作。比方这个文件较大(2GB),可能需要耗时 30min 才能下载成功。而在这 30min 里面,它会卡在 await 后面。这个 await 标记了协程,那就意味着它可以被暂停,那既然该任务可以被暂停,我们就把它分离出去。我这个线程继续执行其它任务,它这个 30min 分出去慢慢的传输,我这个程序再运行其他操作。

上面的代码,Python 3.6 会给你报错。报错信息如下:

Traceback (most recent call last):
  File "C:/Code/pycharm_daima/爬虫大师班/14-异步编程/test.py", line 26, in <module>
    asyncio.run(main())
AttributeError: module 'asyncio' has no attribute 'run'

为什么会出现这样的报错呢?

因为从 Python 3.7+ 之后 Python 已经完全支持异步了,Python 3.6 之前只是支持部分异步,许多的方法是非常冗长的。

一个异步函数调用另一个异步函数:

import asyncio
async def net():
	return 11
async def main():
	# net() # error
	await net() # right
asyncio.run(main())

tips:

异步主要做得是 I/O 类型,CPU 密集型就不需要使用异步。

一个异步调用另一个异步函数,不能直接被调用,必须添加 await

我们使用代码验证一下,不加 await 调用试一试:

import asyncio

async def net():
	return 11
async def main():
	net() # error
asyncio.run(main())

输出结果:

C:/Code/pycharm_daima/爬虫大师班/14-异步编程/test.py:31: RuntimeWarning: coroutine 'net' was never awaited
  net() # error
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

我们添加上 await 即可正常运行:

import asyncio

async def net():
	return 11
async def main():
	# net() # error
	await net() # right
asyncio.run(main())

运行结果:

C:\Users\clela\AppData\Local\Programs\Python\Python37\python.exe C:/Code/pycharm_daima/爬虫大师班/14-异步编程/test.py

Process finished with exit code 0

运行成功并没有报错,接下来我们要输出得到的结果该怎么编写代码呢?直接赋值即可:

import asyncio

async def net():
	return 11
async def main():
	# net() # error
	a = await net() # right
	print(a)
asyncio.run(main())

输出结果:

11

Ps:async 标记异步,await 标记等待。

如果我们不想使用 await 来运行异步函数,那这个时候我们就可以按如下方法来运行代码:

import asyncio

async def net():
	return 11

async def main():
	task = asyncio.create_task(net())
	await task # right
	
asyncio.run(main())
1.4.4 并发的执行任务

一系列的协程可以通过 await 链式调用,但是有的时候我们需要在一个协程里等待多个协程,比如我们在一个协程里等待 1000 个异步网络请求,对于访问次序没有要求的时候,就可以使用关键字 wait 来解决了。wait 可以暂停一个协程,直到后台操作完成。

Task 的使用

import asyncio


async def num(n):
    print(f"当前的数字是:{n}")
    await asyncio.sleep(n)
    print(f"等待时间:{n}")


async def main():
    tasks = [num(i) for i in range(10)] #协程列表
    #await asyncio.gather(*tasks) #有序并发
    await asyncio.wait(tasks) #并发运行协程列表的协程


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(main())
    finally:
        loop.close()

输出:

当前的数字是:0
当前的数字是:4
当前的数字是:8
当前的数字是:1
当前的数字是:5
当前的数字是:7
当前的数字是:2
当前的数字是:6
当前的数字是:9
当前的数字是:3
等待时间:0
等待时间:1
等待时间:2
等待时间:3
等待时间:4
等待时间:5
等待时间:6
等待时间:7
等待时间:8
等待时间:9

如果运行的话会发现首先会打印 10 次数字,但是并不是顺序执行的,这也说明 asyncio.wait 并发执行的时候是乱序的。如果想保证顺序只要使用 gather 把 task 写成解包的形式就行了,也就是上面的注释部分的代码。

1.4.5 如何在协程中使用普通的函数呢?

我们知道在普通函数中调用普通函数之间,函数名加括号即可,像下面这样:

def foo():
   print("这是一个普通函数")
   return "test"

def main():
   print("调用foo函数") 
   res=foo()
   print(f"{接收到来自foo函数的值}:res")
if __name__=="__main__"
   main()

那么在协程中如何使用一个普通函数呢? 在协程中可以通过一些方法去调用普通的函数。可以使用的关键字有 call_soon 等。

1.4.5 call_soon

可以通过字面意思理解调用立即返回。 下面来看一下具体的使用例子:

import asyncio
import functools


def callback(args, *, kwargs="defalut"):
    print(f"普通函数做为回调函数,获取参数:{args},{kwargs}")


async def main(loop):
    print("注册callback")
    loop.call_soon(callback, 1)
    wrapped = functools.partial(callback, kwargs="not defalut")
    loop.call_soon(wrapped, 2)
    await asyncio.sleep(0.2)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(main(loop))
finally:
    loop.close()

输出结果:

注册callback
普通函数做为回调函数,获取参数:1,defalut
普通函数做为回调函数,获取参数:2,not defalut

通过输出结果我们可以发现我们在协程中成功调用了一个普通函数,顺序地打印了 1 和 2。

看过这些例子之后,也许你就有疑问了,协程没有缺点的么?

1.5 协程的缺点

同样的总结下大概以下 2 点。

1.5.1 无法使用 CPU 的多核

协程的本质是个单线程,它不能同时用上单个 CPU 的多个核,协程需要和进程配合才能运行在多 CPU 上。当然我们日常所编写的绝大部分应用都没有这个必要,就比如网络爬虫来说,限制爬虫的速度还有其他的因素,比如网站并发量、网速等问题都会是爬虫速度限制的因素。除非做一些密集型应用,这个时候才可能会用到多进程和协程。

1.5.2 处处都要使用非阻塞代码

写协程就意味着你要一值写一些非阻塞的代码,使用各种异步版本的库,比如后面的异步爬虫教程中用的 aiohttp 就是一个异步版本的request库等。 不过这些缺点并不能影响到使用协程的优势。

2. 协程与异步

上面想必你已经完全掌握了,接下来,我们用睡眠来模仿一下耗时的 IO 操作。

import asyncio

# 定义异步函数

async def hello(i):
	print('hello', i)
	await asyncio.sleep(3) # 假设我们下载文件需要3s
	print('world', i)

if __name__ == '__main__':
	tasks = []
	for i in range(4):
		tasks.append(hello(i)) # 把要下载请求的页面放入我们的 tasks,然后交给 asyncio 处理
	loop = asyncio.get_event_loop() # 获取时间循环
	loop.run_until_complete(asyncio.wait(tasks)) # run_until_complete:把所有程序都运行完毕,然后再停止运行。
	loop.close()

输出结果:

hello 3
hello 1
hello 0
hello 2
world 3
world 0
world 1
world 2

tips:

注意区别 time.sleep() 这个是不能使用到异步里面的 sleep,如果你直接用 time 模块里面的 说了 sleep 那代码是真正睡眠了,不会执行其他任务了。所以需要使用 asyncio.sleep() 的睡眠才可以。requests 包也是同理,所以接下来我会给大家讲解一个新的包(aiohttp),我们将用 aiohttp 来代替 requests。

接下来我们来分析一下输出结果:

hello 3 # 当程序执行在这个任务时需要 3s 的时间,所以进入等待,然后继续执行下一个任务
hello 1 # 当上一个任务在等待的时候,这个任务在也遇到了要等待 3s ,接着执行下一个任务,以此类推。
hello 0
hello 2
world 3 # 当任务等待完成(恢复)那 world 就输出出来了)
world 0
world 1
world 2

这时候细心的小伙伴有可能会说,我们添加任务进去的时候是 0、1、2、3,可是在执行的时候却是 3、1、0、2这就是我上面说的异步是不可控,随机的。

小结:

我在使用异步的时候,上面一共说到了三种:

执行单个任务:

  1. await 执行异步
  2. asyncio.create_task(function)

执行多个任务:

  1. 获取事件循环:loop = asyncio.get_event_loop()、loop.run_until_complete(asyncio.wait(list))

3. 异步爬虫

pip install aiohttp

抓取目标网站:百思不得姐

import asyncio
import aiohttp
from bs4 import BeautifulSoup
headers = {
	'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36',
}

async def crawl(i):
	url = f'http://www.budejie.com/{i}'
	async with aiohttp.ClientSession(headers = headers)as session:
		async with session.get(url)as response:
			print(response.status)
			text = await response.text()
			print('start', i)
	soup = BeautifulSoup(text, 'lxml')
	lis = soup.select(".j-r-list ul li div .u-txt a")
	for li in lis:
		print(li.get_text())
if __name__ == '__main__':
	tasks = [crawl(i) for i in range(1, 10)]
	loop = asyncio.get_event_loop()
	loop.run_until_complete(asyncio.wait(tasks))
    loop.close()

输出结果:

C:\Users\clela\AppData\Local\Programs\Python\Python37\python.exe C:/Code/pycharm_daima/爬虫大师班/14-异步编程/异步爬虫实战.py
200
start 6
怀疑人生 
金月 
游先生 
加强 
南南 
心之痕 
怀疑人生 
能认真点吗 
雨婷思梦 
原装正版无添加 
随便了 
滒特 誃瑙菏 
糖水菠萝 
诠忄 
知鱼之乐 
墨染锦年 
.
.
.
.
.
.

懒洋洋 
死神小一生 
圆圆呐 
仙境里的童话 
汪坚他爹是我 
嘘呀 
路上城静 
顾蒙蒙 
Pescado 

Process finished with exit code 0

补充:

if __name__ == '__main__':
	tasks = [crawl(i) for i in range(1, 10)]
	loop = asyncio.get_event_loop()
	# 方法一:
	loop.run_until_complete(asyncio.wait(tasks))
	loop.close()
	# 方法二:
	loop.run_until_complete(asyncio.gather(*tasks))
	loop.close()
	# 方法三:
	for task in tasks:
		loop.run_until_complete(asyncio.gather(task))
	loop.close()

那到这里,同学们已经掌握了:多线程、多进程、线程池、进程池、异步。那有同学可能会问:可不可以把这几个方法结合起来呢?

那我告诉你们的是,异步只能用异步的方法执行,不过大家是否用过 concurrent.future 模块呢?这个模块是底层是 异步,所以这也是我接下来所要说的。

4. 异步使用线程池与进程池

Concurrent.futures 这个模块可以和异步连接,具有线程池和进程池。管理并发编程,处理非确定性的执行流程,同步功能。

使用 requests 的异步

我们先导入所需要的库:

import asyncio, requests
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
# ThreadPoolExecutor :线程池
# ProcessPoolExecutor:进程池
from bs4 import BeautifulSoup
"""
project = 'Code', file_name = '使用requests的异步', author = 'AI悦创'
time = '2020/3/10 20:34', product_name = PyCharm
# code is far away from bugs with the god animal protecting
    I love animals. They taste delicious.
              ┏┓      ┏┓
            ┏┛┻━━━┛┻┓
            ┃        ┃
            ┃  ┳┛  ┗┳  ┃
            ┃      ┻      ┃
            ┗━┓      ┏━┛
                ┃      ┗━━━┓
                ┃  神兽保佑    ┣┓
                ┃ 永无BUG!   ┏┛
                ┗┓┓┏━┳┓┏┛
                  ┃┫┫  ┃┫┫
                  ┗┻┛  ┗┻┛
"""
import asyncio, requests,aiohttp
import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from bs4 import BeautifulSoup
from requests.exceptions import RequestException

headers = {
	'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36',
}

def crawl(i):
	url = f'http://www.budejie.com/{i}'
	try:
		html = requests.get(url, headers=headers)
		if html.status_code == 200:
			soup = BeautifulSoup(html.text, 'lxml')
			lis = soup.select(".j-r-list ul li div .u-txt a")
			for li in lis:
				print(li.get_text())
		return "ok"
	except RequestException:
		return None

async def main():
	loop = asyncio.get_event_loop() # 获取循环事件
	tasks = []
	with ThreadPoolExecutor(max_workers=10)as t:
		# 10 个线程,10 个任务
		for i in range(1, 10):
			tasks.append(loop.run_in_executor(t, crawl, i))
	# 		task.append(loop.run_in_executor(放入你的线程,爬虫函数,爬虫函数参数)

	# 以下代码可以不写
	# await asyncio.wait(tasks)
	# for result in await asyncio.wait(tasks):
	# 	print(result)# 当你执行的爬虫函数有返回信息时使用
	# 	pass

if __name__ == '__main__':
	start_time = time.time()
	loop = asyncio.get_event_loop()
	loop.run_until_complete(main())
	loop.close()
	print(time.time() - start_time)
import asyncio, requests,aiohttp
import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from bs4 import BeautifulSoup
from requests.exceptions import RequestException

headers = {
	'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36',
}

def crawl(i):
	url = f'http://www.budejie.com/{i}'
	try:
		html = requests.get(url, headers=headers)
		if html.status_code == 200:
			soup = BeautifulSoup(html.text, 'lxml')
			lis = soup.select(".j-r-list ul li div .u-txt a")
			for li in lis:
				pass
			# 	print(li.get_text())
		return "ok"
	except RequestException:
		return None

if __name__ == '__main__':
	start_time_1 = time.time()
	for i in range(1, 10):
		crawl(i)
	print("单线程时间:>>>", time.time() - start_time_1)

	start_time_2 = time.time()
	with ThreadPoolExecutor(max_workers=10)as t:
		for i in range(1, 10):
			t.submit(crawl, i)
	print("线程池时间:>>>", time.time() - start_time_2)

	start_time_3 = time.time()
	with ProcessPoolExecutor(max_workers=10)as t:
		for i in range(1, 10):
			t.submit(crawl, i)
	print("进程池时间:>>>", time.time() - start_time_3)

输出结果:

单线程时间:>>> 2.1695995330810547
线程池时间:>>> 0.5049772262573242
进程池时间:>>> 0.920097827911377

我们来分析一下输出结果,我们会分析进程池花费的时间会比线程池更多,这是为什么呢?

  1. 多线程非常适合 I/O 密集型,不适合 CPU 密集型;
  2. 进程池创建销毁的资源开销大,创建一个进程所耗费的资源要比创建一个线程耗费的时间大很多,销毁它也需要很长的时间。(准备工作非常多)

4. 小结

对于协程的入门来说,这些知识已经够用了。当然协程涉及到的知识不止这些,这里只是为了大家提前对协程有一定的了解,后面将继续讲解协程的其他知识,一切的协程知识基础都是为后面的异步爬虫教程做准备,只有熟悉了使用协程才能在后面教程中快速上手操作。 接下来将进一步提到本文没有提及的事件循环、Task、Future、Awaitable 等一系列知识点,以及协程的高层 API 知识。敬请期待!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI悦创|编程1v1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值