引言
講到進程,不得不先說下linux
的fork()
函數,一個進程調用fork()函數后,系統先給新的進程分配資源,例如存儲數據和代碼的空間。然后把原來的進程的所有值都復制到新的新進程中,只有少數值與原來的進程的值不同。相當于克隆了一個自己。
import os
import time
# 此代碼只能運行于linux/unix
pid = os.fork() # 創建一個子進程
print("test")
if pid == 0:
print("子進程 {}, 父進程是 {}".format(os.getpid(), os.getppid()))
else:
print("我是父進程: {}".format(pid))
time.sleep(2)
# 執行結果
# test
# 我是父進程: 6428
# test
# 子進程
# 6428, 父進程是 6427
注意到,fork()
后面的代碼執行了兩次,但行為不一樣。如果fork()
返回0,說明是子進程。而子進程會將父進程的數據原樣拷貝一遍,所以和線程不一樣,不能通過全局變量來通信。
由于GIL鎖的存在,python
的多線程一直被人詬病,在CPU密集型操作中,推薦多進程,在IO密集型操作中,推薦多線程。和多線程的threading
模塊一樣,python
為多進程也提供了multiprocessing
多進程包。
multiprocessing模塊
有了多線程的使用基礎,多進程的API很好理解。
普通使用
import multiprocessing
import time
def get_html(n):
time.sleep(n)
print("sub_progress success")
return n
if __name__ == "__main__": #注意,在windows下這句話必須加!!!
progress = multiprocessing.Process(target=get_html, args=(2,))
print(progress.pid)
progress.start() # 啟動子進程
print(progress.pid) # 打印子進程的PID
progress.join() # 等待子進程結束
print("main progress end")
# 執行結果
# None
# 5444
# sub_progress success
# main progress end
-
multiprocessing.Process()
傳入執行入口函數和參數。 -
start()
方法啟動子進程。 -
progress.pid
屬性可以獲取子進程的ID號,在未啟動前,未None,啟動后,得到系統分配的ID號。 -
join()
方法等待子進程結束。
進程池
multiprocessing
模塊中提供Pool
類來支持進程池編程。
import multiprocessing
import time
def get_html(n):
time.sleep(n)
print("sub_progress success")
return n
if __name__ == "__main__": #注意,在windows下這句話必須加!!!
pool = multiprocessing.Pool(multiprocessing.cpu_count())
result = pool.apply_async(get_html, args=(3,))
pool.close() # 在join前必須運行close()
pool.join()
print(result.get()) # 得到子進程的返回值
# 執行結果
# sub_progress success
# 3
- 使用
multiprocessing.Pool()
來創建進程池,可以設置進程池的大小,默認為CPU的核心個數,一般進程也不會超過CPU的核心個數,超過了效率也不會高。 -
apply_async
方法傳入執行入口函數和參數,傳入之后,就進入了子進程等待隊列中,等待執行。返回值是ApplyResult
對象,存儲子進程的結果。 -
join
()方法等待所有子進程的結束,調用前必須使用close()
方法。 -
ApplyResult
對象的get()
方法,獲取子進程的返回值。
multiprocessing
模塊同樣提供了類似于map
的方法:
import multiprocessing
import time
def get_html(n):
time.sleep(n)
print("{}s sub_progress success".format(n))
return n
if __name__ == "__main__": #注意,在windows下這句話必須加!!!
pool = multiprocessing.Pool(multiprocessing.cpu_count())
# for result in pool.imap_unordered(get_html, [1, 3, 2]):
for result in pool.imap(get_html, [1, 3, 2]):
print("{}s sleep success".format(result))
# 執行結果
# 1s sub_progress success
# 1s sleep success
# 2s sub_progress success
# 3s sub_progress success
# 3s sleep success
# 2s sleep success
-
imap
方法,結果輸出順序與傳入的可迭代對象的順序相同。 -
imap_unordered
方法,結果輸出順序與子進程結束的時間順序相同。
進程池的更好實現
雖然multiprocessing
模塊中提供Pool
類,但是使用進程池的時候,使用concurrent.futures
模塊中的ProcessPoolExecutor
類更加方便。在介紹線程池的時候,介紹了ThreadPoolExecutor
的使用,而ProcessPoolExecutor
的接口實現和ThreadPoolExecutor
完全一樣,只用多創建多線程改完創建多進程即可。值得注意的是:在windows下進行多進程編程的時候,主程序一定要加if __name__ == "__main__":
。
總結
- 在CPU密集型操作時,使用多進程可以加快速度。
- 多進程API與多線程API基本相同,在使用線程池的時候,可以使用
multiprocessing.Pool
,也可以使用Future
模塊下的ProcessPoolExecutor
,推薦后者。 - 在windows下多進程編程要加
if __name__ == "__main__":
。