快速理解多进程与多线程以及协程的使用场合和特色
首先咱们来了解下python中的进程,线程以及协程!html
从计算机硬件角度:node
计算机的核心是CPU,承担了全部的计算任务。
一个CPU,在一个时间切片里只能运行一个程序。python
从操做系统的角度:数据库
进程和线程,都是一种CPU的执行单元。安全
进程:表示一个程序的上下文执行活动(打开、执行、保存...)服务器
线程:进程执行程序时候的最小调度单位(执行a,执行b...)网络
一个程序至少有一个进程,一个进程至少有一个线程。多线程
并行 和 并发:并发
并行:多个CPU核心,不一样的程序就分配给不一样的CPU来运行。可让多个程序同时执行。app
cpu1 -------------
cpu2 -------------
cpu3 -------------
cpu4 -------------
并发:单个CPU核心,在一个时间切片里一次只能运行一个程序,若是须要运行多个程序,则串行执行。
cpu1 ---- ----
cpu1 ---- ----
多进程/多线程:
表示能够同时执行多个任务,进程和线程的调度是由操做系统自动完成。
进程:每一个进程都有本身独立的内存空间,不一样进程之间的内存空间不共享。
进程之间的通讯有操做系统传递,致使通信效率低,切换开销大。
线程:一个进程能够有多个线程,全部线程共享进程的内存空间,通信效率高,切换开销小。
共享意味着竞争,致使数据不安全,为了保护内存空间的数据安全,引入"互斥锁"。
一个线程在访问内存空间的时候,其余线程不容许访问,必须等待以前的线程访问结束,才能使用这个内存空间。
互斥锁:一种安全有序的让多个线程访问内存空间的机制。
Python的多线程:
GIL 全局解释器锁:线程的执行权限,在Python的进程里只有一个GIL。
一个线程须要执行任务,必须获取GIL。
好处:直接杜绝了多个线程访问内存空间的安全问题。
坏处:Python的多线程不是真正多线程,不能充分利用多核CPU的资源。
可是,在I/O阻塞的时候,解释器会释放GIL。
因此:
多进程:密集CPU任务,须要充分使用多核CPU资源(服务器,大量的并行计算)的时候,用多进程。 multiprocessing
缺陷:多个进程之间通讯成本高,切换开销大。
多线程:密集I/O任务(网络I/O,磁盘I/O,数据库I/O)使用多线程合适。
threading.Thread、multiprocessing.dummy
缺陷:同一个时间切片只能运行一个线程,不能作到高并行,可是能够作到高并发。
协程:又称微线程,在单线程上执行多个任务,用函数切换,开销极小。不经过操做系统调度,没有进程、线程的切换开销。genvent,monkey.patchall
多线程请求返回是无序的,那个线程有数据返回就处理那个线程,而协程返回的数据是有序的。
缺陷:单线程执行,处理密集CPU和本地磁盘IO的时候,性能较低。处理网络I/O性能仍是比较高.
下面以这个网站为例,采用三种方式爬取。爬取前250名的电影。。
https://movie.douban.com/top250?start=0
经过分析网页发现第2页的url start=25,第3页的url start=50,第3页的start=75。所以能够得出这个网站每一页的数局是经过递增start这个参数获取的。
通常不看第一页的数据,第一页的没有参考价值。
此次咱们主要爬取,电影名字跟评分。只是使用不一样方式去对比下不一样点,因此数据方面就不过多提取或者保存。只是简单的将其爬取下打印出来看看。
第一:采用多进程 , multiprocessing 模块。 固然这个耗时更网络好坏有关。在所有要请求都正常的状况下耗时15s多。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
#!/usr/bin/env python2
# -*- coding=utf-8 -*-
from
multiprocessing
import
Process, Queue
import
time
from
lxml
import
etree
import
requests
class
DouBanSpider(Process):
def
__init__(
self
, url, q):
# 重写写父类的__init__方法
super
(DouBanSpider,
self
).__init__()
self
.url
=
url
self
.q
=
q
self
.headers
=
{
'Host'
:
'movie.douban.com'
,
'Referer'
:
'https://movie.douban.com/top250?start=225&filter='
,
'User-Agent'
:
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36'
,
}
def
run(
self
):
self
.parse_page()
def
send_request(
self
,url):
'''
用来发送请求的方法
:return: 返回网页源码
'''
# 请求出错时,重复请求3次,
i
=
0
while
i <
=
3
:
try
:
print
u
"[INFO]请求url:"
+
url
return
requests.get(url
=
url,headers
=
self
.headers).content
except
Exception as e:
print
u
'[INFO] %s%s'
%
(e,url)
i
+
=
1
def
parse_page(
self
):
'''
解析网站源码,并采用xpath提取 电影名称和平分放到队列中
:return:
'''
response
=
self
.send_request(
self
.url)
html
=
etree.HTML(response)
# 获取到一页的电影数据
node_list
=
html.xpath(
"//div[@class='info']"
)
for
move
in
node_list:
# 电影名称
title
=
move.xpath(
'.//a/span/text()'
)[
0
]
# 评分
score
=
move.xpath(
'.//div[@class="bd"]//span[@class="rating_num"]/text()'
)[
0
]
# 将每一部电影的名称跟评分加入到队列
self
.q.put(score
+
"\t"
+
title)
def
main():
# 建立一个队列用来保存进程获取到的数据
q
=
Queue()
base_url
=
'https://movie.douban.com/top250?start='
# 构造全部url
url_list
=
[base_url
+
str
(num)
for
num
in
range
(
0
,
225
+
1
,
25
)]
# 保存进程
Process_list
=
[]
# 建立并启动进程
for
url
in
url_list:
p
=
DouBanSpider(url,q)
p.start()
Process_list.append(p)
# 让主进程等待子进程执行完成
for
i
in
Process_list:
i.join()
while
not
q.empty():
print
q.get()
if
__name__
=
=
"__main__"
:
start
=
time.time()
main()
print
'[info]耗时:%s'
%
(time.time()
-
start)
|
采用多线程时,耗时10.4s

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
#!/usr/bin/env python2
# -*- coding=utf-8 -*-
from
threading
import
Thread
from
Queue
import
Queue
import
time
from
lxml
import
etree
import
requests
class
DouBanSpider(Thread):
def
__init__(
self
, url, q):
# 重写写父类的__init__方法
super
(DouBanSpider,
self
).__init__()
self
.url
=
url
self
.q
=
q
self
.headers
=
{
'Cookie'
:
'll="118282"; bid=ctyiEarSLfw; ps=y; __yadk_uid=0Sr85yZ9d4bEeLKhv4w3695OFOPoedzC; dbcl2="155150959:OEu4dds1G1o"; as="https://sec.douban.com/b?r=https%3A%2F%2Fbook.douban.com%2F"; ck=fTrQ; _pk_id.100001.4cf6=c86baf05e448fb8d.1506160776.3.1507290432.1507283501.; _pk_ses.100001.4cf6=*; __utma=30149280.1633528206.1506160772.1507283346.1507290433.3; __utmb=30149280.0.10.1507290433; __utmc=30149280; __utmz=30149280.1506160772.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utma=223695111.1475767059.1506160772.1507283346.1507290433.3; __utmb=223695111.0.10.1507290433; __utmc=223695111; __utmz=223695111.1506160772.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); push_noty_num=0; push_doumail_num=0'
,
'Host'
:
'movie.douban.com'
,
'Referer'
:
'https://movie.douban.com/top250?start=225&filter='
,
'User-Agent'
:
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.104 Safari/537.36'
,
}
def
run(
self
):
self
.parse_page()
def
send_request(
self
,url):
'''
用来发送请求的方法
:return: 返回网页源码
'''
# 请求出错时,重复请求3次,
i
=
0
while
i <
=
3
:
try
:
print
u
"[INFO]请求url:"
+
url
html
=
requests.get(url
=
url,headers
=
self
.headers).content
except
Exception as e:
print
u
'[INFO] %s%s'
%
(e,url)
i
+
=
1
else
:
return
html
def
parse_page(
self
):
'''
解析网站源码,并采用xpath提取 电影名称和平分放到队列中
:return:
'''
response
=
self
.send_request(
self
.url)
html
=
etree.HTML(response)
# 获取到一页的电影数据
node_list
=
html.xpath(
"//div[@class='info']"
)
for
move
in
node_list:
# 电影名称
title
=
move.xpath(
'.//a/span/text()'
)[
0
]
# 评分
score
=
move.xpath(
'.//div[@class="bd"]//span[@class="rating_num"]/text()'
)[
0
]
# 将每一部电影的名称跟评分加入到队列
self
.q.put(score
+
"\t"
+
title)
def
main():
# 建立一个队列用来保存进程获取到的数据
q
=
Queue()
base_url
=
'https://movie.douban.com/top250?start='
# 构造全部url
url_list
=
[base_url
+
str
(num)
for
num
in
range
(
0
,
225
+
1
,
25
)]
# 保存线程
Thread_list
=
[]
# 建立并启动线程
for
url
in
url_list:
p
=
DouBanSpider(url,q)
p.start()
Thread_list.append(p)
# 让主线程等待子线程执行完成
for
i
in
Thread_list:
i.join()
while
not
q.empty():
print
q.get()
if
__name__
=
=
"__main__"
:
start
=
time.time()
main()
print
'[info]耗时:%s'
%
(time.time()
-
start)
|
采用协程爬取,耗时15S,

用了多进程,多线程,协程,实现的代码都同样,没有测试出明显的那个好!都不分上下,可能跟网络,或者服务器配置有关。
但理论上来讲线程,协程在I/O密集的操做性能是要高于进程的。