如何优雅的编写Python并发程序(Gevent)

转载过程中,图片丢失,代码显示错乱。

为了更好的学习内容,请访问原创版本:

http://www.missshi.cn/api/view/blog/59a6b2e8e519f50d040000fe

Ps:初次访问由于js文件较大,请耐心等候(8s左右)


众所周知,Python是非常擅长网络爬虫的。
而对于一个大规模的网络爬虫而言,使用常规的Python编程会使得效率极其低效。
本文主要讲解如何在Python中利用Gevent来大幅提高程序运行的效率。

 

让我们从一个标准的Python顺序执行代码开始吧!

一段正常运行的Python代码如下:


  
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import requests
import time
def request_url ( url ):
"""
#访url
:paramurl:
:return:
"""
response = requests. get ( url )
return response. content
if __name__ == "__main__":
#访url4*10=40url
url_list = [ 'https://github.com/',
'http://www.missshi.cn:8888/',
'https://www.python.org/',
'https://www.yahoo.com/' ] * 10
response_list = [ ]
begin_time = time. time ( ) #
for url in url_list:
print url
response_content = request_url ( url )
response_list. append ( response_content )
end_time = time. time ( ) #
used_time = end_time - begin_time #
print used_time

运行该程序时,我们可以发现它会依次遍历url_list中的每一个url。

在上一个请求得到响应后,再去发送下一个请求。

在我本地调试时,访问40个url的总耗时为33.207s。

而这其中,绝大部分的时间都是在等待接口的响应结果。

 

Gevent又称之为协程,它是一个有助于大幅度提高网络传输应用型服务的性能。

Gevent的主要实现原理是在运行一个任务时,如果遇到IO或网络相关的操作,会在发送请求后切换到别的任务去处理,而不是在原任务中等待接收响应。这样一来,对于一个大量网络请求的任务而言,它可能起到很好的并发效果。

 

首先,我们通过一个简单的示例程序了解一下Gevent库的使用。


  
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import gevent
def function1 ( str1, str2 ):
print "************"
print str1
print str2
if __name__ == "__main__":
task_list = [ ] #
for i in range ( 5 ):
str1 = "string1_" + str ( i )
str2 = "string2_" + str ( i )
task_list. append ( gevent. spawn ( function1, str1, str2 )) #
result = gevent. joinall ( task_list )

对于gevent而言,我们需要准备一个任务列表。

任务列表中每一个元素都是一个gevent.spawn()对象。

其中,gevent.spawn()函数可以接收一至多个参数。

第一个参数为任务需要执行的函数,后续的参数为函数对应的输入参数。

最终,当我们得到完成的任务列表后,可以调用gevent.joinall()来执行该列表中的任务。

 

运行该程序,我们可以得到如下结果:


  
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
************
string1_0
string2_0
************
string1_1
string2_1
************
string1_2
string2_2
************
string1_3
string2_3
************
string1_4
string2_4

通过观察结果,我们不难发现,该程序仍然是顺序执行的所有的任务,而没有在不同的任务中进行切换。

那么是什么原因呢?

1. 没有包含IO或网络操作,因此没有自动触发切换任务。

2. 也没有手工触发切换进程。

 

首先,我们先来学习如何手工触发切换任务:

只需要添加一行代码gevent.sleep()后,得到的代码如下:


  
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import gevent
def function1 ( str1, str2 ):
print "************"
print str1
gevent. sleep ( ) #
print str2
if __name__ == "__main__":
task_list = [ ]
for i in range ( 5 ):
str1 = "string1_" + str ( i )
str2 = "string2_" + str ( i )
task_list. append ( gevent. spawn ( function1, str1, str2 ))
result = gevent. joinall ( task_list )

重新运行程序后观察结果:


  
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
************
string1_0
************
string1_1
************
string1_2
************
string1_3
************
string1_4
string2_0
string2_1
string2_2
string2_3
string2_4

观察结果后,我们可以发现程序在运行到gevent.sleep()后,不会继续在当前任务中执行,而是切换至别的任务中运行。

 

当然,在应用代码中,我们很少会手工触发gevent.sleep()来切换任务。

而是通过引入一些相关的函数,可以自动起到在面临网络请求的任务时,自动切换任务。

具体的实现方法我们根据如下代码来进行讲解:


  
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import gevent
import requests
import time
def request_url ( url ):
"""
#访url
:paramurl:
:return:
"""
from gevent import monkey
monkey. patch_socket ( ) #
print url
response = requests. get ( url )
print "responseofurl:", url
return response. content
if __name__ == "__main__":
url_list = [ 'https://github.com/',
'http://www.missshi.cn:8888/',
'https://www.python.org/',
'https://www.yahoo.com/' ] * 10
task_list = [ ]
response_list = [ ]
begin_time = time. time ( )
for url in url_list:
task_list. append ( gevent. spawn ( request_url, url ))
result = gevent. joinall ( task_list )
end_time = time. time ( )
used_time = end_time - begin_time
print used_time

需要注意的是,当我们引入猴子补丁后,会对已经以后的方法进行改写。

因此,不建议在全局范围内引入猴子补丁,最好是在哪部分为并发执行函数,则在哪部分引入猴子补丁。

在引入猴子补丁后,当运行到网络请求时,则会切换至其他任务继续执行,而不是在当前任务中继续等待。

 

最后,我们来讲解一下针对任务列表中的任务,在执行完成后如何获取并发任务的返回值。


  
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if __name__ == "__main__":
url_list = [ 'https://github.com/',
'http://www.missshi.cn:8888/',
'https://www.python.org/',
'https://www.yahoo.com/' ] * 10
task_list = [ ]
response_list = [ ]
begin_time = time. time ( )
for url in url_list:
task_list. append ( gevent. spawn ( request_url, url ))
result = gevent. joinall ( task_list )
response_list = [ element. value for element in result ] #resultResponse
end_time = time. time ( )
used_time = end_time - begin_time
print used_time

对于gevent.joinall()函数而言,得到的结果是一个迭代器。

其中,迭代器中每一个元素都包含一个属性value,其对应值为每个任务函数的返回值。


更多更详细的内容,请访问原创网站:

http://www.missshi.cn/api/view/blog/59a6b2e8e519f50d040000fe

Ps:初次访问由于js文件较大,请耐心等候(8s左右)


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值