开始介绍Python的多程序之前,我们需要了解python中的GIL的存在,也就是多执行绪的时候,同一时间只能有一个执行绪在CPU上执行,而且是单个CPU上执行,不管你的CPU有多少核数。如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多程序。
1.Python多程序模组
Python中的多程序是通过multiprocessing包来实现的,和多执行绪的threading.Thread差不多,它可以利用multiprocessing.Process物件来建立一个程序物件。这个程序物件的方法和执行绪物件的方法差不多也有start(), run(), join()等方法,其中有一个方法不同Thread执行绪物件中的守护执行绪方法是setDeamon,而Process程序物件的守护程序是通过设定daemon属性来完成的。
下面说说Python多程序的实现方法,和多执行绪类似
2.Python多程序实现方法一
from multiprocessing import Process
def fun1(name):
print(\'测试%s多程序\' %name)
if __name__ == \'__main__\':
process_list = []
for i in range(5): #开启5个子程序执行fun1函式
p = Process(target=fun1,args=(\'Python\',)) #例项化程序物件
p.start()
process_list.append(p)
for i in process_list:
p.join()
print(\'结束测试\')
结果
测试Python多程序
测试Python多程序
测试Python多程序
测试Python多程序
测试Python多程序
结束测试
Process finished with exit code 0
上面的程式码开启了5个子程序去执行函式,我们可以观察结果,是同时打印的,这里实现了真正的并行操作,就是多个CPU同时执行任务。我们知道程序是python中最小的资源分配单元,也就是程序中间的资料,内存是不共享的,每启动一个程序,都要独立分配资源和拷贝访问的资料,所以程序的启动和销毁的代价是比较大了,所以在实际中使用多程序,要根据服务器的配置来设定。
3.Python多程序实现方法二
还记得python多执行绪的第二种实现方法吗?是通过类继承的方法来实现的,python多程序的第二种实现方式也是一样的
from multiprocessing import Process
class MyProcess(Process): #继承Process类
def __init__(self,name):
super(MyProcess,self).__init__()
self.name = name
def run(self):
print(\'测试%s多程序\' % self.name)
if __name__ == \'__main__\':
process_list = []
for i in range(5): #开启5个子程序执行fun1函式
p = MyProcess(\'Python\') #例项化程序物件
p.start()
process_list.append(p)
for i in process_list:
p.join()
print(\'结束测试\')
结果
测试Python多程序
测试Python多程序
测试Python多程序
测试Python多程序
测试Python多程序
结束测试
Process finished with exit code 0
效果和第一种方式一样。
我们可以看到Python多程序的实现方式和多执行绪的实现方式几乎一样。
Process类的其他方法
构造方法:
Process([group [, target [, name [, args [, kwargs]]]]])
group: 执行绪组
target: 要执行的方法
name: 程序名
args/kwargs: 要传入方法的引数
例项方法:
is_alive():返回程序是否在执行,bool型别。
join([timeout]):阻塞当前上下文环境的程序程,直到呼叫此方法的程序终止或到达指定的timeout(可选引数)。
start():程序准备就绪,等待CPU排程
run():strat()呼叫run方法,如果例项程序时未制定传入target,这star执行t预设run()方法。
terminate():不管任务是否完成,立即停止工作程序
属性:
daemon:和执行绪的setDeamon功能一样
name:程序名字
pid:程序号
关于join,daemon的使用和python多执行绪一样,这里就不在叙述了,大家可以看看python多执行绪系列文章。
4.Python多执行绪的通讯
程序是系统独立排程核分配系统资源(CPU、内存)的基本单位,程序之间是相互独立的,每启动一个新的程序相当于把资料进行了一次克隆,子程序里的资料修改无法影响到主程序中的资料,不同子程序之间的资料也不能共享,这是多程序在使用中与多执行绪最明显的区别。但是难道Python多程序中间难道就是孤立的吗?当然不是,python也提供了多种方法实现了多程序中间的通讯和资料共享(可以修改一份资料)
程序对列Queue
Queue在多执行绪中也说到过,在生成者消费者模式中使用,是执行绪安全的,是生产者和消费者中间的资料管道,那在python多程序中,它其实就是程序之间的资料管道,实现程序通讯。
from multiprocessing import Process,Queue
def fun1(q,i):
print(\'子程序%s 开始put资料\' %i)
q.put(\'我是%s 通过Queue通讯\' %i)
if __name__ == \'__main__\':
q = Queue()
process_list = []
for i in range(3):
p = Process(target=fun1,args=(q,i,)) #注意args里面要把q物件传给我们要执行的方法,这样子程序才能和主程序用Queue来通讯
p.start()
process_list.append(p)
for i in process_list:
p.join()
print(\'主程序获取Queue资料\')
print(q.get())
print(q.get())
print(q.get())
print(\'结束测试\')
结果
子程序0 开始put资料
子程序1 开始put资料
子程序2 开始put资料
主程序获取Queue资料
我是0 通过Queue通讯
我是1 通过Queue通讯
我是2 通过Queue通讯
结束测试
Process finished with exit code 0
上面的程式码结果可以看到我们主程序中可以通过Queue获取子程序中put的资料,实现程序间的通讯。
管道Pipe
管道Pipe和Queue的作用大致差不多,也是实现程序间的通讯,下面之间看怎么使用吧
from multiprocessing import Process, Pipe
def fun1(conn):
print(\'子程序传送讯息:\')
conn.send(\'你好主程序\')
print(\'子程序接受讯息:\')
print(conn.recv())
conn.close()
if __name__ == \'__main__\':
conn1, conn2 = Pipe() #关键点,pipe例项化生成一个双向管
p = Process(target=fun1, args=(conn2,)) #conn2传给子程序
p.start()
print(\'主程序接受讯息:\')
print(conn1.recv())
print(\'主程序传送讯息:\')
conn1.send("你好子程序")
p.join()
print(\'结束测试\')
结果
主程序接受讯息:
子程序传送讯息:
子程序接受讯息:
你好主程序
主程序传送讯息:
你好子程序
结束测试
Process finished with exit code 0
上面可以看到主程序和子程序可以相互发送讯息
Managers
Queue和Pipe只是实现了资料互动,并没实现资料共享,即一个程序去更改另一个程序的资料。那么久要用到Managers
from multiprocessing import Process, Manager
def fun1(dic,lis,index):
dic[index] = \'a\'
dic[\'2\'] = \'b\'
lis.append(index) #[0,1,2,3,4,0,1,2,3,4,5,6,7,8,9]
#print(l)
if __name__ == \'__main__\':
with Manager() as manager:
dic = manager.dict()#注意字典的宣告方式,不能直接通过{}来定义
l = manager.list(range(5))#[0,1,2,3,4]
process_list = []
for i in range(10):
p = Process(target=fun1, args=(dic,l,i))
p.start()
process_list.append(p)
for res in process_list:
res.join()
print(dic)
print(l)
结果:
{0: \'a\', \'2\': \'b\', 3: \'a\', 1: \'a\', 2: \'a\', 4: \'a\', 5: \'a\', 7: \'a\', 6: \'a\', 8: \'a\', 9: \'a\'}
[0, 1, 2, 3, 4, 0, 3, 1, 2, 4, 5, 7, 6, 8, 9]
可以看到主程序定义了一个字典和一个列表,在子程序中,可以新增和修改字典的内容,在列表中插入新的资料,实现程序间的资料共享,即可以共同修改同一份资料
5.程序池
程序池内部维护一个程序序列,当使用时,则去程序池中获取一个程序,如果程序池序列中没有可供使用的进程序,那么程式就会等待,直到程序池中有可用程序为止。就是固定有几个程序可以使用。
程序池中有两个方法:
apply:同步,一般不使用
apply_async:异步
from multiprocessing import Process,Pool
import os, time, random
def fun1(name):
print(\'Run task %s (%s)...\' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print(\'Task %s runs %0.2f seconds.\' % (name, (end - start)))
if __name__==\'__main__\':
pool = Pool(5) #建立一个5个程序的程序池
for i in range(10):
pool.apply_async(func=fun1, args=(i,))
pool.close()
pool.join()
print(\'结束测试\')
结果
Run task 0 (37476)...
Run task 1 (4044)...
Task 0 runs 0.03 seconds.
Run task 2 (37476)...
Run task 3 (17252)...
Run task 4 (16448)...
Run task 5 (24804)...
Task 2 runs 0.27 seconds.
Run task 6 (37476)...
Task 1 runs 0.58 seconds.
Run task 7 (4044)...
Task 3 runs 0.98 seconds.
Run task 8 (17252)...
Task 5 runs 1.13 seconds.
Run task 9 (24804)...
Task 6 runs 1.46 seconds.
Task 4 runs 2.73 seconds.
Task 8 runs 2.18 seconds.
Task 7 runs 2.93 seconds.
Task 9 runs 2.93 seconds.
结束测试
对Pool物件呼叫join()方法会等待所有子程序执行完毕,呼叫join()之前必须先呼叫close(),呼叫close()之后就不能继续新增新的Process了。
程序池map方法
案例来源于网络,侵权请告知,谢谢
因为网上看到这个例子觉得不错,所以这里就不自己写案例,这个案例比较有说服力
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) #关键点,images是一个可迭代物件
pool.close()
pool.join()
上边这段程式码的主要工作就是将遍历传入的资料夹中的图片档案,一一生成缩图,并将这些缩图储存到特定资料夹中。这我的机器上,用这一程式处理 6000 张图片需要花费 27.9 秒。 map 函式并不支援手动执行绪管理,反而使得相关的 debug 工作也变得异常简单。
map在爬虫的领域里也可以使用,比如多个URL的内容爬取,可以把URL放入元祖里,然后传给执行函式。
小编还为大家准备了一些学习程式设计从入门到精通的学习资料,以及程序员面试,涉及到的绝大部分面试题及答案做成的文件和学习笔记档案,希望可以帮助到大家。
转发此文,关注并私信小编“学习”,即可马上领取,仅限300人哦