[python] 多進程編程

引言

講到進程,不得不先說下linuxfork()函數,一個進程調用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
  1. multiprocessing.Process()傳入執行入口函數和參數。
  2. start()方法啟動子進程。
  3. progress.pid屬性可以獲取子進程的ID號,在未啟動前,未None,啟動后,得到系統分配的ID號。
  4. 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
  1. 使用multiprocessing.Pool()來創建進程池,可以設置進程池的大小,默認為CPU的核心個數,一般進程也不會超過CPU的核心個數,超過了效率也不會高。
  2. apply_async方法傳入執行入口函數和參數,傳入之后,就進入了子進程等待隊列中,等待執行。返回值是ApplyResult對象,存儲子進程的結果。
  3. join()方法等待所有子進程的結束,調用前必須使用close()方法。
  4. 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
  1. imap方法,結果輸出順序與傳入的可迭代對象的順序相同。
  2. 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__":

參考

Python3高級編程和異步IO并發編程

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容