公眾號:pythonislover
前面寫了三篇關於python多執行緒的文章,大概概況了多執行緒使用中的方法,文章連結如下:
一篇文章搞懂Python多執行緒簡單實現和GIL - mp.weixin.qq.com/s/Hgp-x-T3s…
一篇文章理清Python多執行緒同步鎖,死鎖和遞迴鎖 - mp.weixin.qq.com/s/RZSBe2MG9…
一篇文章理清Python多執行緒之同步條件,訊號量和佇列 - mp.weixin.qq.com/s/vKsNbDZnv…
今天開始會開啟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放入元祖裡,然後傳給執行函式。