[python] 多程式程式設計

weixin_33797791發表於2018-05-06

引言

講到程式,不得不先說下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併發程式設計

相關文章