Python 并发编程系列--4、多进程编程方法

上一篇直通车 Python 并发编程系列–3、多线程编程方法

前言

上一篇介绍了多线程编程,这一篇介绍多进程编程,当然应用场景是CPU密集型任务。

分类

都是采用multiprocessing模块,只不过应用的类和方法不同

  • 使用Pool对象的map方法
  • 使用Pool对象的apply_async方法
  • 使用Process类

下面分别介绍

使用Pool对象的map方法

上一篇类似,multiprocessing模块的Pool对象具有map方法,应用map方法可以很便捷地实现多线程程序。下面给出两个例子,分别来自python多进程(三种方法)一行Python实现并行化
首先看第一个例子,使用多线程计算0到9的平方

1
2
3
4
5
6
7
8
9
10
11
12
13
#coding:utf-8

from multiprocessing import Pool
import time

def f(x):
time.sleep(1)
print x
return x*x

if __name__=='__main__':
p=Pool(5)
print (p.map(f,range(10)))

代码运行结果

1
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

可以看到列表是按照顺序打印的,这正是采用这种方式执行的好处:既实现了多进程,又保证了程序的执行顺序。

另一个例子,是生成上千张图片的缩略图,单线程版本如下:

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
import os 
import PIL

from multiprocessing import Pool
from PIL import Image

SIZE = (75,75)
SAVE_DIRECTORY = 'thumbs'

def get_image_paths(folder):
return (os.path.join(folder, f)
for f in os.listdir(folder)
if 'jpeg' in f)

def create_thumbnail(filename):
im = Image.open(filename)
im.thumbnail(SIZE, Image.ANTIALIAS)
base, fname = os.path.split(filename)
save_path = os.path.join(base, SAVE_DIRECTORY, fname)
im.save(save_path)

if __name__ == '__main__':
folder = os.path.abspath(
'11_18_2013_R000_IQM_Big_Sur_Mon__e10d1958e7b766c3e840')
os.mkdir(os.path.join(folder, SAVE_DIRECTORY))

images = get_image_paths(folder)

for image in images:
create_thumbnail(Image)

在原文作者的机器上,处理6000章图片需要27.9秒。下面是使用map方法后的多进程版本。

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
import os 
import PIL

from multiprocessing import Pool
from PIL import Image

SIZE = (75,75)
SAVE_DIRECTORY = 'thumbs'

def get_image_paths(folder):
return (os.path.join(folder, f)
for f in os.listdir(folder)
if 'jpeg' in f)

def create_thumbnail(filename):
im = Image.open(filename)
im.thumbnail(SIZE, Image.ANTIALIAS)
base, fname = os.path.split(filename)
save_path = os.path.join(base, SAVE_DIRECTORY, fname)
im.save(save_path)

if __name__ == '__main__':
folder = os.path.abspath(
'11_18_2013_R000_IQM_Big_Sur_Mon__e10d1958e7b766c3e840')
os.mkdir(os.path.join(folder, SAVE_DIRECTORY))

images = get_image_paths(folder)

pool = Pool()
pool.map(creat_thumbnail, images)
pool.close()
pool.join()

在原文作者的机器上,这次运行只用了5.6秒,可见速度大大提升!
注意:在使用这种方法时可能存在一些影响程序性能甚至影响程序正常执行的隐患所在,因此最好保证业务逻辑库的引用以及一些业务逻辑代码不放在Pool的map方法中,最好放在map里调用的函数中,详细信息见这个讨论

使用Pool的apply_async方法

下面是前文提到的博客中的代码,同样完成计算0到9的平方,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#coding:utf-8

from multiprocessing import Pool
import time

def f(x):
print x*x
time.sleep(1)
return x*x

if __name__=='__main__':
p=Pool(processes=5)
res_list=[]
for i in range(10):
res=p.apply_async(f,[i,])
res_list.append(res)

for item in res_list:
print item.get(),

运行结果同上,仍为有序序列。不过不同的是上一次是执行完成后返回列表,而这一次一边计算一边显示(这正是使用apply_async方法的特点,函数调用是异步的)。想要详细观察效果的话建议读者在本地机器上运行比较。

使用Process类

下面这个例子是使用multiprocessing模块中的Process类来实现的多进程,实例通过打印主进程和从进程的pid来验证属于两个进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#coding:utf-8

from multiprocessing import Process
import os

def info(title):
print title
print 'module_name:',__name__
if hasattr(os,'getppid'):#仅在Unix主机上才有此属性
print 'parent process:',os.getppid()
print 'process id:',os.getpid()

def f(name):
info('funcion f')
print 'hello',name

if __name__=='__main__':
info('main line')
p=Process(target=f,args=('bob',))
p.start()
p.join()

结果如下

1
2
3
4
5
6
7
main line
module_name: __main__
process id: 11688
funcion f
module_name: __main__
process id: 10520
hello bob

通过打印出的结果可以看到通过Process对象成功创建了一个子进程。

题外话

下面谈一些周边的内容,首先通过实例验证一下多进程数据区域的独立性。

1
2


第一次运行结果

1
2
3
4
5
6
7
8
9
10
[0[]1[
]2
[][5]
[3
4]]
[
6]
[7[8]
]
[9]

第二次运行结果

1
2
3
4
5
6
7
8
9
10
[[01[3]
][]
2[
[5]
]4
][
6[]7
[]8
][
9]

下一篇预告

下一篇会谈到协程,至于什么时候就不知道了2333

文章目录
  1. 1. 上一篇直通车 Python 并发编程系列–3、多线程编程方法
  • 前言
  • 分类
    1. 1. 使用Pool对象的map方法
    2. 2. 使用Pool的apply_async方法
    3. 3. 使用Process类
    4. 4. 题外话
    5. 5. 下一篇预告
  • ,