Skip to content

Commit 41f781a

Browse files
committed
Merge pull request Voidly#13 from lujun9972/patch-chapter5
Patch chapter5
2 parents e8b69a9 + 30cc968 commit 41f781a

File tree

3 files changed

+28
-25
lines changed

3 files changed

+28
-25
lines changed

第五章/使用多进程解决斐波那契序列多输入问题.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ import concurrent.futures
66
from multiprocessing import, cpu_count, current_process, Manager
77
```
88

9-
上面一些导入模块在之前的章节中提及过,然而,下面中的一些模块我们需要特别注意
9+
上面一些导入模块在之前的章节中提及过,然而,下面的这些模块我们需要特别注意
1010
* cpu_count: 该方法允许获得机器cpu的数量
1111
* current_process: 该方法可以获得当前进程的信息,比如进程名称
1212
* Manager: 该类通过对象的方式允许功在多进程之间共享python对象
1313

1414
下面的代码中我们可以注意到第一个方法有点不同,它将产生15个1到20之间的整数,这些整数将被当作fibo_dict的key使用。
15-
接下来让我们一起来看producer_task方法,如下:
15+
接下来让我们一起来看producer_task方法,如下:
1616
```python
1717
def producer_task(q, fibo_dict):
18-
for i in range(5):
18+
for i in range(15):
1919
value = random.randint(1, 20)
2020
fibo_dict[value] = None
2121

@@ -34,8 +34,8 @@ def consumer_task(q, fibo_dict):
3434
a, b = 0, 1
3535
for item in range(value):
3636
a, b = b, a+b
37-
fibo_dict[value] = a
38-
print("consumer [%s] getting value [%d] from queue..." % (current_process().name, value))
37+
fibo_dict[value] = a
38+
print("consumer [%s] getting value [%d] from queue..." % (current_process().name, value))
3939
```
4040

4141
更进一步,我们来看main函数中的代码,main函数中下面几个变量被定义:

第五章/使用进程池实现网络爬虫.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
正如concurrent.futures模块提供的ThreadPoolExecutor类方便了操控多线程程序,多进程同样有一个类叫ProcessPoolExecutor。ProcessPoolExecutor类同样由concurrent.futures包提供,我们将使用该类执行我们的网络爬虫程序。为了完成这个任务,我们创建了一个叫process_pool_executor_web_crawler.py的python模块。
1+
##使用ProcessPoolExecutor模块设计网络爬虫
2+
正如concurrent.futures模块提供的ThreadPoolExecutor类方便了操控多线程程序,多进程同样有一个类叫ProcessPoolExecutor。ProcessPoolExecutor类同样由concurrent.futures包提供,我们将使用该类执行我们的网络爬虫程序。为了完成这个任务,我们创建了一个叫process\_pool\_executor\_web\_crawler.py的python模块。
23

3-
代码要导入的包如之前章节中我们介绍的,如requests, Manager模块等等。对于任务的定义,我们延用上章线程程序中的代码,只是改动其中小部分代码,还有一点不一样,进程程序中,我们向任务函数传入参数,而不是使用全局变量。代码如下所是:
4-
group_urls_task函数定义如下:
4+
代码要导入的包如之前章节中我们介绍的,如requests, Manager模块等等。对于任务的定义,我们延用上章线程程序中的代码,只是改动其中小部分代码,还有一点不一样,进程程序中,我们向任务函数传入参数,而不是使用全局变量。代码如下所示:
5+
6+
group\_urls\_task函数定义如下:
57
```python
68
def group_urls_task(urls, result_dict, html_link_regex)
79
```
8-
crawl_task函数定义如下:
10+
11+
crawl\_task函数定义如下:
912
```python
1013
def crawl_task(url, html_link_regex)
1114

1215
```
1316

14-
现在我们将来看下面一小部分代码,做了细微的变化。在main函数中,我们获得Manager类的实例,该实例使得我们获得可以被多进程功效的queue和dict。我们使用Manager.Queue()方法获得queue实例来存储我们将要爬得url。使用Manager.dict()方法获取dict,来存储爬虫的结果。下面的代码将介绍上面的定义:
17+
现在我们再来看下面一小部分代码,与前一章比有了细微的变化。在main函数中,我们获得Manager类的实例,该实例使得我们获得可以被多进程共享的queue和dict。我们使用Manager.Queue()方法获得queue实例来存储我们将要爬得的url。使用Manager.dict()方法获取dict,来存储爬虫的结果。下面的代码将展示上面的定义:
1518
```python
1619
if __name__ == '__main__':
1720
manager = Manager()
@@ -28,7 +31,7 @@ html_link_regex = \
2831
number_of_cpus = cpu_count()
2932
```
3033

31-
最后一块代码中,我们会注意到API中的一致性模块concurrent.futures。下面的代码正是我们上章使用ThreadPoolExecutor模块时使用到的。我们可以把ThreadPoolExecutor变为ProcessPoolExecutor,并不会影响到CPU绑定的GIL问题。注意下面的程序,创建ProcessPoolExecutor时会根据机器cpu数限定进程的数目。第一个exucutor是为了手机将被爬的URL,把这些URLs保存在一个字典中,key为url而value为None。第二个executor执行爬虫程序。
34+
最后一块代码中,我们会注意到concurrent.futures模块中各API签名具有很强的一致性。下面的代码正是我们上章使用ThreadPoolExecutor模块时使用到的。我们只需把ThreadPoolExecutor变为ProcessPoolExecutor,就能改变程序内部行为并解决计算密集型进程的GIL问题。注意下面的程序,创建ProcessPoolExecutor时会根据机器cpu数限定进程的数目。第一个exucutor是为了收集将被爬的URL,把这些URLs保存在一个字典中,key为url而value为None。第二个executor执行爬虫程序。
3235
首先是第一个executor:
3336
```python
3437
with concurrent.futures.ProcessPoolExecutor(max_workers=number_of_cpus) as group_link_processes:

第五章/实现多进程间通信.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
python中的multiprocessing模块支持两种方式在进程间通信,都是基于消息传递机制。之前我们介绍过,消息传递机制缺乏同步机制,在交换的进程中不可复制数据
1+
python中的multiprocessing模块支持两种方式在进程间通信,都是基于消息传递机制。之前我们介绍过,由于缺乏同步机制因此不得不采取消息传递机制,在进程间传递复制的数据
22
##使用multiprocessing.Pipe模块
33

44
pipe管道在两个端点间搭建一种通信机制,通过在进程间建立通道使得进程间可以相互通信。
@@ -9,35 +9,35 @@ import os, random
99
from multiprocessing import Process, Pipe
1010
```
1111

12-
通过os模块的os.getpid()方法使得我们获得进程的PID。os.getpid()将以一种透明的方式返回程序的PID,在我们的程序中,它分别返回producer_task进程和consumer_task进程的PID
13-
下面我们将定义producer_task方法,该方法返回1到10之间的一个随机数。producer_task方法的关键是调用conn.send方法,conn以参数的形式在主函数中传入producer_task方法。producer_task方法如下
12+
通过os模块的os.getpid()方法使得我们获得进程的PID。os.getpid()将以一种透明的方式返回程序的PID,在我们的程序中,它分别返回producer\_task进程和consumer\_task进程的PID
13+
下面我们将定义producer\_task方法,该方法返回1到10之间的一个随机数。producer\_task方法的关键是调用conn.send方法,conn以参数的形式在主函数中被传給producer\_task方法。producer\_task方法如下
1414
```python
1515
def producer_task(conn):
1616
value = random.randint(1, 10)
1717
conn.send(value)
1818
print('Value [%d] send by PID [%d]' % (value, os.getpid()))
1919
conn.close()
2020
```
21-
consumer进程将要执行的任务也很简单,它唯一的任务就是接收A进程传递过程的参数,接收本进程的PID,最终打印出来。consumer进程的中传入的consumer_task方法如下:
21+
consumer进程将要执行的任务也很简单,它唯一的任务就是接收A进程传递过来的参数,接收本进程的PID,最终打印出来。consumer进程的中传入的consumer_task方法如下:
2222
```python
2323
def consumer_task(conn)
2424
print('Value [%d] received by PID [%d]' % (conn.recv(), os.getpid()))
2525
```
2626

27-
最后一块将介绍如何调用Pipe()方法创建两个连接对象分别用于producer进程和consumer进程,然后通过参数形式各自传递到consumer_task方法和producer_task方法中去,主函数具体如下所是:
27+
最后一块将介绍如何调用Pipe()方法创建两个连接对象分别用于producer进程和consumer进程,然后通过参数形式各自传递到consumer\_task方法和producer\_task方法中去,主函数具体如下所是:
2828
```python
29-
if __name__ == '__main__':
30-
producer_conn, consumer_conn = Pipe()
31-
consumer = Process(target=consumer_task,args=(consumer_conn,))
32-
producer = Process(target=producer_task,args=(producer_conn,))
29+
if __name__ == '__main__':
30+
producer_conn, consumer_conn = Pipe()
31+
consumer = Process(target=consumer_task,args=(consumer_conn,))
32+
producer = Process(target=producer_task,args=(producer_conn,))
3333

34-
consumer.start()
35-
producer.start()
34+
consumer.start()
35+
producer.start()
3636

37-
consumer.join()
37+
consumer.join()
3838
producer.join()
3939
```
40-
定义好进程之后,我们便可以调用进程对象的start方法开始执行进程,join方法用于分别等待producer进程和consumer进程执行完毕。下面的截图中我们将看到程序的输出:
40+
定义好进程之后,我们便可以调用进程对象的start方法开始执行进程,join方法用于分别等待producer进程和consumer进程执行完毕。下面的截图中我们将看到程序的输出:
4141

4242
##理解multiprocessing.Queue模块
43-
之前小节中我们分析了如何在进程间创建通信通道来传递消息,现在我们将分析如何更有效的传递消息,这里我们使用mutilprocessing模块下的Queue对象。multoprocessing.Queue对象方法和queue.Queue对象方法类似。然后内在实现却不尽相同,比如multiprocess模块使用了内部线程feeder,把缓冲区中的数据传入目标线程相关连接的管道中。管道和队列机制均使用了消息传递机制,节省了使用同步机制带来的开销。
43+
之前小节中我们分析了如何在进程间创建通信通道来传递消息,现在我们将分析如何更有效的传递消息,这里我们使用mutilprocessing模块下的Queue对象。multoprocessing.Queue对象方法和queue.Queue对象方法类似。然后内在实现却不尽相同,比如multiprocess模块使用了内部线程feeder,把缓冲区中的数据传入目标进程相关连接的管道中。管道和队列机制均使用了消息传递机制,节省了使用同步机制带来的开销。

0 commit comments

Comments
 (0)