我们常见的 Linux、Windows、Mac OS 操作系统,都是支持多进程的多核操作系统。所谓多进程,就是系统可以同时运行多个任务。例如我们的电脑上运行着 QQ、浏览器、音乐播放器、影音播放器等。在操作系统中,每个任务就是一个进程。每个进程至少做一件事,多数进程会做很多事,例如影音播放器,要播放画面,同时要播放声音。

Python 多线程可以成倍提高程序的运行速度,而且在多数情况下都是有效的。比如说,一个工厂里同一时刻只能有一个工人在工作,如果这个工厂里各个车间的自动化程度极高且任务耦合度极低,工人进去只是按几下按钮,就可以等待机器完成其余工作,那情况就不一样了,这种场景下一个工人可以管理好多个车间,而且大多数时间都是等,甚至还能抽空打打羽毛球看场电影。

今天给大家带来一门很详细的Python异步编程教程,包含:线程安全、GIL 全局解释器锁、同步和异步、阻塞和非阻塞、生成器如何进化成协程、yield from 语法、协程装饰器、asyncio 模块的任务和事件循环、任务的状态、async / await 关键字、绑定回调等知识点,你可以在实验楼中免费学习。

Python 异步编程

课程准备

本课程详细讲解基于 Python 3.5 的异步编程模式,需预先掌握 Python3 的基础语法中多进程、多线程和生成器的概念。本节课程主要讲解为什么需要协程以及协程从何而来。

知识点

  • 线程安全

  • GIL 全局解释器锁

  • 多线程提高程序执行效率

  • 同步和异步,阻塞和非阻塞

  • 生成器原理

  • 生成器进化成协程

  • 预激协程

  • 协程的返回值

  • yield from 语法

多进程和多线程

我们常见的 Linux、Windows、Mac OS 操作系统,都是支持多进程的多核操作系统。所谓多进程,就是系统可以同时运行多个任务。例如我们的电脑上运行着 QQ、浏览器、音乐播放器、影音播放器等。在操作系统中,每个任务就是一个进程。每个进程至少做一件事,多数进程会做很多事,例如影音播放器,要播放画面,同时要播放声音,在一个进程中,就有很多线程,每个线程做一件事,在一个进程中有多个线程运行就是多线程。可以在实验环境终端执行 ps-ef 命令来查看当前系统中正在运行的进程。

计算机的两大核心为运算器和存储器。常说的手机配置四核、八核,指的就是 CPU 的数量,它决定了手机的运算能力;128G、256G 超大存储空间,指的就是手机存储数据的能力。当我们运行一个程序来计算 3 + 5,计算机操作系统会启动一个进程,并要求运算器派过来一个 CPU 来完成任务;当我们运行一个程序来打开文件,操作系统会启动存储器的功能将硬盘中的文件数据导入到内存中。

一个 CPU 在某一时刻只能做一项任务,即在一个进程(或线程)中工作,当它闲置时,会被系统派到其它进程中。单核计算机也可以实现多进程,原理是第 1 秒的时间段内运行 A 进程,其它进程等待:第 2 秒的时间段内运行 B 进程,其它进程等待。。。第 5 秒的时间段内又运行 A 进程,往复循环。当然实际上 CPU 在各个进程间的切换是极快的,在毫秒(千分之一)、微秒(百万分之一)级,以至于我们看起来这些程序就像在同时运行。现代的计算机都是多核配置,四核八核等,但计算机启动的瞬间,往往就有几十上百个进程在运行了,所以进程切换是一定会发生的,CPU 在忙不迭停地到处赶场。注意,什么时候进行进程 、线程切换是由操作系统决定的,无法人为干预。

线程安全

我们都知道在 MySQL 中有 “原子操作” 的概念,打个比方:韩梅向李红转账 100 块钱,在 MySQL 中需要两步操作:韩梅账户减少 100 元,李红账户增加 100 元。如果第一步操作完成后,意外情况导致第二步没有做,这是不允许发生的,如何保证其不允许发生呢?将两步操作设计成一个事务,事务里可以有多个步骤,其中任何一步出现问题,事务都将失败,前面的步骤全部回滚,就像什么事都没发生。这种操作就叫做原子操作,这种特性就叫做原子性。

在 Python 多线程中,变量是共享的,这也是相较多进程的一个优点,线程占用资源要少得多,但也导致多个 CPU 同时操作多个线程时会引起结果无法预测的问题,也就是说 Python 的线程不安全。

GIL 全局解释器锁

如何解决线程安全问题?CPython 解释器使用了加锁的方法。每个进程有一把锁,启动线程先加锁,结束线程释放锁。打个比方,进程是一个厂房,厂房大门是开着的,门内有锁,工人进入大门后可以在内部上锁。厂房里面有 10 个车间对应 10 个线程,每个 CPU 就是一个工人。GIL(Global Interpreter Lock)全局锁就相当于厂房规定:工人要到车间工作,从厂房大门进去后要在里面反锁,完成工作后开锁出门,下一个工人再进门上锁。也就是说,任意时刻厂房里只能有一个工人,但这样就保证了工作的安全性,这就是 GIL 的原理。当然了,GIL 的存在有很多其它益处,包括简化 CPython 解释器和大量扩展的实现。

根据上面的例子可以看出 GIL 实现了线程操作的安全性,但多线程的效率被大打折扣,一个工厂里只能有一个工人干活,很难想象。这也是 David Beazley(《Python 参考手册》和《Python Cookbook》的作者)说 “Python 线程毫无用处” 的原因。

注意,GIL 不是语言特性,而是解释器的设计特点,有些 Python 解释器例如 JPython 就没有 GIL ,除了 Python 其它语言也有 GIL 设计,例如 Ruby 。

多线程提高工作效率

实际情况并非上面讲得那么惨,Python 多线程可以成倍提高程序的运行速度,而且在多数情况下都是有效的。接着上面的例子说,一个工厂里同一时刻只能有一个工人在工作,如果这个工厂里各个车间的自动化程度极高且任务耦合度极低,工人进去只是按几下按钮,就可以等待机器完成其余工作,那情况就不一样了,这种场景下一个工人可以管理好多个车间,而且大多数时间都是等,甚至还能抽空打打羽毛球看场电影。

比如爬虫程序爬取页面数据这个场景中,CPU 做的事就是发起页面请求和处理响应数据,这两步是极快的,中间网络传输数据的过程是耗时且不占用 CPU 的。一个工人可以在吃完早饭后一分钟内快速到 1000 个车间按下发起请求的按钮,吃完午饭睡一觉,日薄西山时差不多收到网络传回的数据,又用一分钟处理数据,整个程序完成。

上面的场景中,CPU 再多也没有用处,一个 CPU 抽空就能完成整个任务了,毕竟程序中需要 CPU 做的事并不多。这就涉及复杂程序的分类:CPU 密集型和 IO 密集型。爬虫程序就是 IO 密集型程序。CPU 密集型程序全是手工操作,工人一刻也不能停歇,这种情况下 Python 多线程就真可以说是毫无用处了。

下面的课程中我们会经常使用 ipython 来演示,首先在终端执行 sudo pip3 install ipython 安装 ipython 。

我们可以使用 time.sleep 方法模拟 IO 操作来写一段程序证明多线程可以提高程序的运行效率:

# File Name: thread.py

import threading

import time

def crawl_url(): # 假设这是爬虫程序,爬取一个 URL

time.sleep(0.02) # 模拟 IO 操作

def main1(): # 单线程程序

for i in range(100):

crawl_url()

def main2(): # 多线程程序

thread_list = []

for i in range(100):

t = threading.Thread(target=crawl_url)

t.start()

thread_list.append(t)

for t in thread_list:

t.join()

if __name__ == '__main__':

start = time.time()

main1()

end = time.time()

print('单线程耗时:{:.4f}s'.format(end - start))

start = time.time()

main2()

end = time.time()

print('多线程耗时:{:.4f}s'.format(end - start))

程序运行结果:

$ python3 thread.py

单线程耗时:2.2983

多线程耗时:0.0340

理论上,main1 的耗时是 main2 的 100 倍,考虑到 main2 创建多线程、线程切换的开销,这个结果也是相当可观的,IO 操作耗时越长,多线程的威力越大。

异步和同步,阻塞和非阻塞

上文的模拟爬虫示例代码中,main1 中的 for 循环运行 100 次爬取网页的操作,前一个完成后才能运行下一个,这就是同步的概念,在 crawl_url 函数内部的 IO 操作为阻塞操作,线程无法向下执行。

main2 中的第一个 for 循环,创建 100 个线程并启动,这步操作是非阻塞的,不会等一个线程运行完成才创建下一个线程,它会一气儿创建 100 个线程;第二个 for 循环将主线程挂起,直到全部子线程完成,此时的主线程就是阻塞的。这种程序运行方式叫做异步,CPU 在遇到 IO 阻塞时不会站在那儿傻等,而是被操作系统派往其它线程中看看有什么事可做。

所谓的异步,就是 CPU 在当前线程阻塞时可以去其它线程中工作,不管怎么设计,在一个线程内部代码都是顺序执行的,遇到 IO 都得阻塞,所谓的非阻塞,是遇到当前线程阻塞时,CPU 去其它线程工作。

协程初步

在多线程程序中,线程切换由操作系统决定,无法人为干预。上文的模拟爬虫示例代码中各个线程间无关联,没有先后顺序,不涉及互相引用,耦合性为零。协程是在线程的基础上编写由程序员决定代码执行顺序、可以互相影响的高耦合度代码的一种高级程序设计模式。

上文说到 “不论如何设计,在一个线程内部,代码都是顺序执行的,遇到 IO 都得阻塞” ,直到出现了协程,这句话变成了伪命题。一个线程内部可以有多个协程,相当于一个车间内部有多个子任务,一个协程遇到 IO 阻塞,CPU 会自动去另一个协程中工作,而且去哪里工作由程序自己说了算,此外连创建线程和线程切换的开销都省了,这是一个多么大的比较优势!

生成器原理

生成器可谓协程的立身之基,所以先谈生成器。这就要提到经典的斐波那契数列:

In [163]: def fibonacci(n):

...: a, b = 0, 1

...: while b < n:

...: a, b = b, a + b

...: yield a

...:

In [164]: f = fibonacci(100)

In [165]: f

Out[165]: 0x112be2b88>

In [166]: for i in f:

...: print(i)

...:

1

1

2

3

5

8

13

21

34

55

89

函数体内部有 yield 关键字的都是生成器函数,fibonacci 是生成器函数。yield 关键字只能出现在函数中,生成器函数的执行结果是生成器,注意这里所讲的 “执行结果” 不是函数的 return 值。生成器终止时必定抛出 StopIteration 异常,for 循环可以捕获此异常,异常的 value 属性值为生成器函数的 return 值。生成器还可以使用 next 方法迭代。生成器会在 yield 语句处暂停,这是至关重要的,未来协程中的 IO 阻塞就出现在这里。

生成器进化成协程

生成器是由迭代器进化而来,所以生成器对象有 __iter__ 和 __next__ 方法,可以使用 for 循环获得值,注意这里所说的 “获得值” 指的是 yield 语句中 yield 关键字后面的 i 。这是在 Python 2.5 时出现的特性,在 Python 3.3 中出现 yield from 语法之前,生成器没有太大用途。但此时 yield 关键字还是实现了一些特性,且至关重要,就是生成器对象有 send 、throw 和 close 方法。这三个方法的作用分别是发送数据给生成器并赋值给 yield 语句、向生成器中抛入异常由生成器内部处理、终止生成器。这三个方法使得生成器进化成协程。

协程有四种存在状态:

  • GEN_CREATED 创建完成,等待执行

  • GEN_RUNNING 解释器正在执行(这个状态在下面的示例程序中无法看到)

  • GEN_SUSPENDED 在 yield 表达式处暂停

  • GEN_CLOSE 执行结束,生成器停止

可以使用 inspect.getgeneratorstate 方法查看协程的当前状态,举例如下:

In [202]: import inspect

In [203]: def generator():

...: i = '激活生成器'

...: while True:

...: try:

...: value = yield i

...: except ValueError:

...: print('OVER')

...: i = value

...:

In [204]: g = generator() # 1

In [205]: inspect.getgeneratorstate(g) # 2

Out[205]: 'GEN_CREATED'

In [206]: next(g) # 3

Out[206]: '激活生成器'

In [207]: inspect.getgeneratorstate(g)

Out[207]: 'GEN_SUSPENDED'

In [208]: g.send('Hello Shiyanlou') # 4

Out[208]: 'Hello Shiyanlou'

In [209]: g.throw(ValueError) # 5

OVER

Out[209]: 'Hello Shiyanlou'

In [210]: g.close() # 6

In [211]: inspect.getgeneratorstate(g)

Out[211]: 'GEN_CLOSED'

In [212]:

代码说明如下:

1、创建生成器

2、查看生成器状态

3、这步操作叫做预激生成器(或协程),这是必须做的。在生成器创建完成后,需要将其第一次运行到 yield 语句处暂停

4、暂停状态的生成器可以使用 send 方法发送数据,此方法的参数就是 yield 表达式的值,也就是 yield 表达式等号前面的 value 变量的值变成 'Hello Shiyanlou',继续向下执行完一次 while 循环,变量 i 被赋值,继续运行下一次循环,yield 表达式弹出变量 i

5、向生成器抛入异常,异常会被 try except 捕获,作进一步处理

6、close 方法终止生成器,异常不会被抛出

因为生成器的调用方也就是程序员自己可以控制生成器的启动、暂停、终止,而且可以向生成器内部传入数据,所以这种生成器又叫做协程,generator 函数既可以叫做生成器函数,也可以叫协程函数,这是生成器向协程的过渡阶段。

预激协程

预先激活生成器(或协程)可以使用 next 方法,也可以使用生成器的 send 方法发送 None 值: g.send(None) 。为简化协程的使用,我们可以尝试编写一个装饰器来预激协程,这样创建的协程会立即进入 GEN_SUSPENDED 状态,可以直接使用 send 方法:

In [212]: from functools import wraps

In [213]: def coroutine(func): # 预激协程装饰器

...: @wraps(func) # wraps 装饰器保证 func 函数的签名不被修改

...: def wrapper(*args, **kw):

...: g = func(*args, **kw)

...: next(g) # 预激协程

...: return g # 返回激活后的协程

...: return wrapper

...:

In [214]: @coroutine # 使用装饰器重新创建协程函数

...: def generator():

...: i = '激活生成器'

...: while True:

...: try:

...: value = yield i

...: except ValueError:

...: print('OVER')

...: i = value

...:

In [215]: g = generator()

In [216]: inspect.getgeneratorstate(g)

Out[216]: 'GEN_SUSPENDED'

协程的返回值

前文 “生成器原理” 这一小节中提到了 StopIteration 异常的 value 属性值为生成器(协程)函数的 return 值,我们可以在使用协程时捕获异常并得到这个值。举例如下:

In [217]: @coroutine

...: def generator():

...: l = [] # 1

...: while True:

...: value = yield # 2

...: if value == 'CLOSE': # 3

...: break

...: l.append(value) # 4

...: return l # 5

...:

In [218]: g = generator()

In [219]: g.send('hello')

In [220]: g.send('shiyanlou')

In [221]: g.send('CLOSE')

---------------------------------------------------------------------------

StopIteration Traceback (most recent call last)

221-863c90462435> in

----> 1 g.send('CLOSE')

StopIteration: ['hello', 'shiyanlou']

In [222]:

代码说明如下:

1、创建列表,保存协程 send 方法每次发送的参数

2、yield 表达式不弹出值,仅作暂停之用

3、如果 send 方法的参数为 CLOSE ,break 终止 while 循环,停止生成器,抛出 StopIteration 异常

4、将 value 添加到列表

5、设置协程函数的返回值,该值在协程终止抛出 StopIteration 异常时赋值给 value 属性

可以这样捕获异常:

In [231]: g = generator()

In [232]: for i in ('hello', 'shiyanlou', 'CLOSE'):

...: try:

...: g.send(i)

...: except StopIteration as e:

...: value = e.value # e 的 value 属性值就是协程函数的 return 值

...: print('END')

...:

END

In [233]: value

Out[233]: ['hello', 'shiyanlou']

In [234]:

yield from

在 Python 3.3 中新增了 yieldfrom 语法,这是全新的语言结构,是 yield 的升级版。相比 yield ,该语法有两大优势,我们来举例说明它的用法。

避免嵌套循环

我们知道 Python 内置模块 itertools 是十分强大的,里面有很多实用的方法,其中之一是 chain 方法,它可以接受任意数量的可迭代对象作为参数,返回一个可以包含所有参数中的元素的迭代器:

In [8]: from itertools import chain

In [9]: c = chain({'one', 'two'}, list('ace'))

In [10]: c

Out[10]: 0x1066020f0>

In [11]: for i in c:

...: print(i)

...:

one

two

a

c

e

In [12]:

使用 yield 关键字实现 chain 方法:

In [16]: def chain(*args):

...: for iter_obj in args:

...: for i in iter_obj:

...: yield i

...:

In [17]: c = chain({'one', 'two'}, list('ace'))

In [18]: c

Out[18]: 0x1066ff570>

In [19]: for i in c:

...: print(i)

...:

one

two

a

c

e

In [20]:

这样就实现了类似 itertools 中的 chain 方法,注意这里 chain 函数的返回值是生成器,现在我们使用 Python 3.3 新增的 yield from 语法优化上文的 chain 函数:

In [20]: def chain(*args):

...: for iter_obj in args:

...: yield from iter_obj

...:

In [21]: c = chain({'one', 'two'}, list('ace'))

In [22]: c

Out[22]: 0x106a95b88>

In [23]: for i in c:

...: print(i)

...:

one

two

a

c

e

In [24]:

可以看到 yield from 语句可以替代 for 循环,避免了嵌套循环。同 yield 一样,yield from 语句也只能出现在函数体内部,有 yield from 语句的函数叫做协程函数或生成器函数。

yield from 后面接收一个可迭代对象,例如上面代码中的 iter_obj 变量,在协程中,可迭代对象往往是协程对象,这样就形成了嵌套协程。

转移控制权

转移控制权是 yield from 语法的核心功能,也是从生成器进化到协程的最重要一步。

首先安装伪造数据的库 faker ,在终端执行 sudo pip3 install faker 即可。

下面举例说明转移控制权的功能,将以下代码写入 transfer_control.py 文件中,这是一个可以将列表进行排序的程序。对代码的注释写入每行代码前面或后面,方便阅读:

# File Name: transfer_control.py

import time

from faker import Faker

from functools import wraps

# 预激协程装饰器

def coroutine(func):

@wraps(func)

def wrapper(*args, **kw):

g = func(*args, **kw)

next(g)

return g

return wrapper

# 子生成器函数,这个生成器是真正做事的生成器

def sub_coro():

l = [] # 创建空列表

while True: # 无限循环

value = yield # 调用方使用 send 方法发生数据并赋值给 value 变量

if value == 'CLOSE': # 如果调用方发生的数据是 CLOSE ,终止循环

break

l.append(value) # 向列表添加数据

return sorted(l) # 返回排序后的列表

# 使用预激协程装饰器

@coroutine

# 带有 yield from 语句的父生成器函数

def dele_coro():

# while True 可以多次循环,每次循环会创建一个新的子生成器 sub_coro()

# 这里 while 只循环一次,这是由调用方,也就是 main 函数决定的

# while 循环可以捕获函数本身创建的父生成器终止时触发的 StopIteration 异常

while True:

# yield from 会自动预激子生成器 sub_coro()

# 所以 sub_coro 在定义时不可以使用预激协程装饰器

# yield from 将捕获子生成器终止时触发的 StopIteration 异常

# 并将异常的 value 属性值赋值给等号前面的变量 l

# 也就是 l 变量的值等于 sub_coro 函数的 return 值

# yield from 还实现了一个重要功能

# 就是父生成器的 send 方法将发送值给子生成器

# 并赋值给子生成器中 yield 语句等号前面的变量 value

l = yield from sub_coro()

print('排序后的列表:', l)

print('------------------')

# 调用父生成器的函数,也叫调用方

def main():

# 生成随机国家代号的方法

fake = Faker().country_code

# 嵌套列表,每个子列表中有三个随机国家代号(字符串)

nest_country_list = [[fake() for i in range(3)] for j in range(3)]

for country_list in nest_country_list:

print('国家代号列表:', country_list)

c = dele_coro() # 创建父生成器

for country in country_list:

c.send(country) # 父生成器的 send 方法将国家代号发送给子生成器

# CLOSE 将终止子生成器中的 while 循环

# 子生成器的 return 值赋值给父生成器 yield from 语句中等号前面的变量 l

c.send('CLOSE')

if __name__ == '__main__':

main()

运行结果如下:

$ python3 transfer_control.py

国家代号列表:['MH', 'EC', 'MD']

排序后的列表:['EC', 'MD', 'MH']

------------------

国家代号列表:['ML', 'GA', 'EC']

排序后的列表:['EC', 'GA', 'ML']

------------------

国家代号列表:['ER', 'BW', 'CO']

排序后的列表:['BW', 'CO', 'ER']

------------------

所谓 “转移控制权” 就是 yield from 语法可以将子生成器的控制权交给调用方 main 函数,在 main 函数内部创建父生成器 c ,控制 c.send 方法传值给子生成器。这是一个巨大的进步,在此基础上,Python 3.4 新增了创建协程的装饰器,这样非生成器函数的协程函数就正式出现了。

总结

本节课程主要介绍了以下知识点:

  • 线程的工作原理,为什么线程可以提高程序的执行效率

  • 协程的由来,相比线程有什么优势

  • 从生成器到协程,Python 2.5 出现的特性

  • 协程的预激和协程函数的返回值

  • yield from 语法

在下一节,我们将介绍 Python 3.4 新增的 asyncio 模块和 Python 3.5 新增的 async / await 语法。

(完整课程,请在实验楼学习:https://www.shiyanlou.com/courses/1278)

实验楼推出「楼+ Python 实战 · 第 14 期」,来自腾讯、IBM、Intel 的一线大牛指导,在 3 个月的时间内通过 直播、视频、文档、作业、实时助教答疑 等服务,带你打通 Python 的任督二脉,全面掌握 Python 技能,成为真正有工作能力的 Python 工程师!

目前仅剩 2 个名额,有意愿的小伙伴抓紧上车了~

课程评价:52人,死磕Python 84天后,结果感受是这样的  /  往期学员评价。

加入「Python新手入门课」,体验楼+ 学习方式:

请添加助教小姐姐微信(sylmm002),免费拉您学习和入群。

python 多线程和协程结合_Python 异步编程,看这门课就够了~相关推荐

  1. python 多线程和协程结合_一文讲透 “进程、线程、协程”

    本文从操作系统原理出发结合代码实践讲解了以下内容: 什么是进程,线程和协程? 它们之间的关系是什么? 为什么说Python中的多线程是伪多线程? 不同的应用场景该如何选择技术方案? ... 什么是进程 ...

  2. Python 多线程 多进程 协程 yield

    python中多线程和多进程的最大区别是稳定性和效率问题 多进程互相之间不影响,一个崩溃了不影响其他进程,稳定性高 多线程因为都在同一进程里,一个线程崩溃了整个进程都完蛋 多进程对系统资源开销大,多线 ...

  3. 【Python 多线程vs协程】爬取西游记

    为了有效的学习和加深对于线程和协程的认知,将同一个任务用不同思路呈现很有帮助. 这里通过使用多线程以及协程两种方式对西游记的篇章内容进行爬取,来对比一下两者的耗时差异. 首先,多线程爬取. 这里没有限 ...

  4. python 多线程和协程结合_如何让 python 处理速度翻倍?内含代码

    阿里妹导读:作为在日常开发生产中非常实用的语言,有必要掌握一些python用法,比如爬虫.网络请求等场景,很是实用.但python是单线程的,如何提高python的处理速度,是一个很重要的问题,这个问 ...

  5. python线程进程协程面试_Python学习经验之谈:关于协程的理解和其相关面试问题...

    都知道Python非常适合初学者学习来入门编程,昨天有伙伴留言说面试了Python岗位,问及了一个关于协程的问题,想了想还是跟大家出一篇协程相关的文章和在Python面试中可能会问及的相关面试问题.都 ...

  6. python进程线程协程区别_Python中 进程 线程 协程

    一.进程 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.在早期面向进程设计的计算机结构中,进程是程序的基本执行实体:在 ...

  7. python 多线程和协程的区别

    一.共同点 都能进行多任务 二.不同点 协程是轻量级的线程,协程的运行依赖于线程,而线程的运行依赖于协程 共享全局变量问题: 协程: 共享全局变量不需要加锁,不会计算错误.对于全局变量,他的内存地址是 ...

  8. python实现异步的几种方式_Python 异步编程

    介绍几种Python异步执行的方式 参考: 通过 threading.Thread 实现 先将需要异步执行的函数用线程的方式包装为一个装饰器,然后拿去装饰需要异步执行的函数即可. 下面构造两个函数 f ...

  9. Python爬虫:单线程、多线程和协程的爬虫性能对比!异步才是永远滴神!

    然后今天我要给大家分享的是如何爬取豆瓣上深圳近期即将上映的电影影讯,并分别用普通的单线程.多线程和协程来爬取,从而对比单线程.多线程和协程在网络爬虫中的性能. 具体要爬的网址是:https://mov ...

最新文章

  1. Linked List Cycle II
  2. java三维滑雪,第六章 三维数据空间分析方法.ppt
  3. Cer Crt Pem Pfx 证书格式转换
  4. Git基础: .git 目录详解
  5. 【费用流】摘取作物(jozj 3447)
  6. python如何判断列表是否为空_Python中如何检查字符串/列表是否为空
  7. linux环境systwm.img解包,[教程] system.img解包打包的方法,方便菜鸟们制作直刷ROM...
  8. 语音识别控制设计之声控小汽车的实现
  9. 解决电脑各种 dll 文件丢失问题
  10. 车辆路径问题之jsprit(一):认识jsprit
  11. matlab里的dsolve,matlab_dsolve
  12. Mac环境下简化ssh连接vlab口令实现免密登录(UNSW)
  13. 使用阿里云物联网平台IoT Studio控制海康摄像头旋转
  14. Unity3D教程笔记——unity初始03
  15. 金海佳学C++primer 练习9.18/9.19
  16. MATLAB糖葫芦哈哈哈
  17. jQuery(JS库) | 一文带你掌握jQuery的使用
  18. php验证ssl证书key和crt,KEY与CRT匹配校验
  19. 三种离婚男人该去面壁思过
  20. 游戏高防服务器租用,如何选择配置?

热门文章

  1. .eep_eep ..已经是三月了
  2. 百度公司的笔试题目等
  3. TAGNN: Target Attentive Graph Neural Networks for Session-based Recommendation论文阅读笔记
  4. 超级玛丽Simple版本workshorp第一次练习
  5. 高级UI- 属性动画炫酷动画案例+淘宝动画+源码解析+策略模式使用
  6. java使用高德地图根据IP地址获取城市
  7. 哪里可以做傅里叶红外FTIR/ATR-IR测试(型号:赛默飞Nicolet iS10)
  8. TensorFlow项目1——鸢尾花识别的拆分详解(来源:北大曹健老师tensorflow学习视频)
  9. 在基于ABP框架的前端项目VueElement项目中采用电子签名的处理
  10. SaaSBase:最受欢迎的协同办公软件有哪些(上篇)