Python快速而美麗[v1.0.0][多進程詳解]

使用fork創建新進程

Python的OS模塊提供了一個fork()函數,該函數的作用在於程序會啓動兩個進程(一個父進程一個子進程)來執行從os.fork()開始的所有代碼,fork()函數不需要參數,會返回一個表明是哪個進程在執行的返回值,如果返回0則表明是fork出來的子進程在執行,如果返回非0,則表明是父進程在執行
該方法僅在UNIX及類UNIX系統上行得通,包括UNIX,Linux和Mac系統

import os

print('父進程(%s)開始執行' % os.getpid())
# 開始fork一個子進程
# 從這行代碼開始,下面代碼都會被兩個進程執行
pid = os.fork()
print('進程進入:%s' % os.getpid())
# 如果pid爲0,表明子進程
if pid == 0:
    print('子進程,其ID爲 (%s), 父進程ID爲 (%s)' % (os.getpid(), os.getppid()))
else:
    print('我 (%s) 創建的子進程ID爲 (%s).' % (os.getpid(), pid))
print('進程結束:%s' % os.getpid())

pid = os.fork()之後的代碼程序會分別使用父進程和子進程來執行

使用multiprocessing.Process創建新進程

Python在muliprocessing模塊下提供了Process來創建新進程,與Thread非常類似,創建新進程有兩種方式

  • 以指定函數作爲target,創建Process對象即可創建新進程
  • 繼承Process類,並重寫它的run()方法來創建進程類,程序創建Process子類的實例作爲進程

同樣的Process也具有如下類似的方法和屬性

  • run():重寫該方法可實現進程的執行體
  • start():該方法用於啓動進程
  • join(timeout):該方法類似於線程的join()方法,當前進程必須等待被join的進程執行完畢才能繼續執行
  • name:該屬性用於設置或訪問進程的名字
  • is_alive():判斷進程死活
  • daemon:該屬性用於判斷或設置進程的後臺狀態
  • pid:返回進程ID
  • authkey:返回進程的授權key
  • terminate():中斷該進程

以指定函數作爲target創建進程

import multiprocessing
import os

# 定義一個普通的action函數,該函數準備作爲進程執行體
def action(max):
    for i in range(max):
        print("(%s)子進程(父進程:(%s)):%d" % 
            (os.getpid(), os.getppid(), i))
if __name__ == '__main__':
    # 下面是主程序(也就是主進程)
    for i in range(100):
        print("(%s)主進程: %d" % (os.getpid(), i))
        if i == 20:
            # 創建並啓動第一個進程
            mp1 = multiprocessing.Process(target=action,args=(100,))
            mp1.start()
            # 創建並啓動第一個進程
            mp2 = multiprocessing.Process(target=action,args=(100,))
            mp2.start()
            mp2.join()
    print('主進程執行完成!')

繼承Process類創建子進程

創建步驟:

  • 定義繼承Process的子類,重寫其run()方法準備作爲進程執行體
  • 創建Process子類的實例
  • 調用Process子類的實例的start()方法來啓動進程
import multiprocessing
import os

class MyProcess(multiprocessing.Process):
    def __init__(self, max):
        self.max = max
        super().__init__()
    # 重寫run()方法作爲進程執行體
    def run(self):
        for i in range(self.max):
            print("(%s)子進程(父進程:(%s)):%d" % 
                (os.getpid(), os.getppid(), i))
if __name__ == '__main__':
    # 下面是主程序(也就是主進程)
    for i in range(100):
        print("(%s)主進程: %d" % (os.getpid(), i))
        if i == 20:
            # 創建並啓動第一個進程
            mp1 = MyProcess(100)
            mp1.start()
            # 創建並啓動第一個進程
            mp2 = MyProcess(100)
            mp2.start()
            mp2.join()
    print('主進程執行完成!')

Context和啓動進程的方式

Python有三種啓動進程的方式

  • spawn:父進程會啓動一個全新的Python解釋器進程,這也是windows平臺唯一的方式,在這種方式下,子進程只能繼承那些處理run()方法所必須的資源,那些不必要的文件掃描器和handle都不會被繼承,這種方式比fork或forkserver方式效率要低
  • fork:父進程使用os.fork()來啓動一個python解釋器進程,該子進程會繼承父進程的所有資源,因此它基本等效於父進程
  • forkserver:用這種方式啓動進程,程序會啓動一個服務器進程,之後當程序再次請求啓動新進程時,父進程都會鏈接到該服務器進程,請求由服務器進程來fork新進程,這種方式無需從父進程繼承資源,跟fork一樣該方式僅在UNIX系統有效

set_start_method函數

multiprocessing模塊提供了set_start_method()函數,用於設置啓動繼承的方式,該代碼必須所有與多進程相關的代碼之前

import multiprocessing
import os

def foo(q):
    print('被啓動的新進程: (%s)' % os.getpid())
    q.put('Python')
if __name__ == '__main__':
    # 設置使用fork方式啓動進程
    multiprocessing.set_start_method('spawn')
    q = multiprocessing.Queue()
    # 創建進程
    mp = multiprocessing.Process(target=foo, args=(q, ))
    # 啓動進程
    mp.start()
    # 獲取隊列中的消息 
    print(q.get())
    mp.join()

程序的新進程想multiprocess.Queue中放入一個數據(Python)主進程取出該Queue中的數據,並輸出該數據

get_context函數

該方法也能設置進程啓動方式,利用get_context()方法來獲取Context對象,調用該方法可傳入spawn、fork、forkserver字符串,Context擁有和multiprocessing相同的API

import multiprocessing
import os

def foo(q):
    print('被啓動的新進程: (%s)' % os.getpid())
    q.put('Python')
if __name__ == '__main__':
    # 設置使用fork方式啓動進程,並獲取Context對象
    ctx = multiprocessing.get_context('fork')
    # 接下來就可用Context對象來代替mutliprocessing模塊了
    q = ctx.Queue()
    # 創建進程
    mp = ctx.Process(target=foo, args=(q, ))
    # 啓動進程
    mp.start()
    # 獲取隊列中的消息 
    print(q.get())
    mp.join()

使用進程池管理進程

程序可以通過multiprocessing模塊的Pool()函數來創建進程池,它實際上是multiprocess.pool.Pool類,它提供瞭如下常用函數:

  • apply(func[, args[, kwds]]):將func函數提交給進程池處理,其中args代表傳給func的位置參數,kwds代表傳給func的關鍵字參數,該方法會被阻塞直到func函數執行完成
  • apply_async(func[, args[, kwds[, callback[, error_callback]]]]):這是apply()函數的異步版,該方法不會被阻塞,其中callback指定func函數完成後的回調函數,error_callback指定func函數出錯後的回調函數
  • map(func, iterable[, chunksize]):類似於Python的map()函數,只是此處使用新進程對iterable的每一個元素執行func函數
  • map_async(func, iterable[, chunksize[, callback[, error_callback]]]):這是map()方法的異步版,該方法不會被阻塞,其中callback指定func函數完成後的回調函數,error_callback指定func函數出錯後的回調函數
  • imap(func, iterable[, chunksize]):map()方法的延遲版本
  • imap_unrodered(func, iterable[, chunksize]):功能類似於imap()方法,但該方法不能保證所有生成的結果(包含多個元素)與原iterable中的元素順序一致
  • starmap(func, iterable[, chunksize]):類似於map()方法,該方法要求iterable的元素也是iterable對象,程序會將每一個元素解包之後作爲func函數的參數
  • close():關閉進程池,該方法讓進程池不能再接收新任務,但會繼續執行當前進程池中所有任務,直到完畢再關閉自己
  • terminate():立即中止進程池
  • join():等待所有進程完成
import multiprocessing
import time
import os

def action(name='default'):
    print('(%s)進程正在執行,參數爲: %s' % (os.getpid(), name))
    time.sleep(3)
if __name__ == '__main__':
    # 創建包含4條進程的進程池
    pool = multiprocessing.Pool(processes=4)
    # 將action分3次提交給進程池
    pool.apply_async(action)
    pool.apply_async(action, args=('位置參數', ))
    pool.apply_async(action, kwds={'name': '關鍵字參數'})
    pool.close()
    pool.join()

import multiprocessing
import time
import os

# 定義一個準備作爲進程任務的函數
def action(max):
    my_sum = 0
    for i in range(max):
        print('(%s)進程正在執行: %d' % (os.getpid(), i))
        my_sum += i
    return my_sum
if __name__ == '__main__':
    # 創建一個包含4條進程的進程池
    with multiprocessing.Pool(processes=4) as pool:
        # 使用進程執行map計算
        # 後面元組有3個元素,因此程序啓動3條進程來執行action函數
        results = pool.map(action, (50, 100, 150))
        print('--------------')
        for r in results:
            print(r)

進程通信

Python爲進程通信提供了兩種機制

  • Queue:一個進程向Queue中放入數據,另一個進程從Queue中讀取數據
  • Pipe:Pipe代表鏈接兩個進程的管道,程序在調用Pipe()函數時會產生兩個鏈接端,分別交給通信的兩個進程,然後進程既可以從該鏈接端讀取數據,也可以向該連接端寫入數據

使用Queue實現進程通信

multiprocess下的Queue和queue下的Queue類似,都提供了qsize()/empty()/full()/put()/put_nowait()/get()/get_nowait()等方法,區別只是一個服務於進程,一個服務於線程

import multiprocessing

def f(q):
    print('(%s) 進程開始放入數據...' % multiprocessing.current_process().pid)
    q.put('Python')
if __name__ == '__main__':
    # 創建進程通信的Queue
    q = multiprocessing.Queue()
    # 創建子進程
    p = multiprocessing.Process(target=f, args=(q,))
    # 啓動子進程
    p.start()
    print('(%s) 進程開始取出數據...' % multiprocessing.current_process().pid)
    # 取出數據
    print(q.get())  # Python
    p.join()

使用Pipe實現進程通信

程序調用multiprocessing.Pipe()函數來創建一個管道,並返回兩個PipeConnection對象,用於連接通信的兩個進程,PipeConnection有如下方法:

  • send(obj):發送一個obj給管道的另一端,另一端使用recv()方法接收,該obj必須是可拿到的,並且如果該對象系列化後超過32MB則可能會報ValueError異常
  • recv():接受另一端通過send()方法發送過來的數據
  • fileno():關於連接所使用的文件掃描器
  • close():關閉連接
  • poll(timeout):返回鏈接中是否還有數據可以讀取
  • send_bytes(buffer[, offset[, size]]):發送字節數據,如果沒有指定offset、size參數,則默認發送buffer字節串的全部數據;如果指定了offset和size參數,則只發送buffer字節串中從offset開始,長度爲size的字節數據,通過該方法發送的數據應該使用recv_bytes()或者recv_bytes_into()方法接收
  • recv_bytes([maxlength]):接收通過send_bytes()方法發送的數據,maxlength指定最多接收的字節數,該方法返回接收到的字節數據
  • recv_bytes_into(buffer[, offset]):功能與recv_bytes()方法類似,只是該方法將接收到的數據放在buffer中
import multiprocessing

def f(conn):
    print('(%s) 進程開始發送數據...' % multiprocessing.current_process().pid)
    # 使用conn發送數據
    conn.send('Python')
if __name__ == '__main__':
    # 創建Pipe,該函數返回兩個PipeConnection對象
    parent_conn, child_conn = multiprocessing.Pipe()
    # 創建子進程
    p = multiprocessing.Process(target=f, args=(child_conn, ))
    # 啓動子進程
    p.start()
    print('(%s) 進程開始接收數據...' % multiprocessing.current_process().pid)
    # 通過conn讀取數據
    print(parent_conn.recv())  # Python
    p.join()

多線程與多進程

import multiprocessing
import time

class Account:
    # 定義構造器
    def __init__(self, account_no, balance, lock):
        # 封裝賬戶編號、賬戶餘額的兩個成員變量
        self.account_no = account_no
        self._balance = balance
        self.lock = lock

    # 因爲賬戶餘額不允許隨便修改,所以只爲self._balance提供getter方法
    def getBalance(self):
        return self._balance
    # 提供一個進程安全的draw()方法來完成取錢操作
    def draw(self, draw_amount):
        # 加鎖
        self.lock.acquire()
        try:
            # 賬戶餘額大於取錢數目
            if self._balance >= draw_amount:
                # 吐出鈔票
                print(multiprocessing.current_process().name\
                    + "取錢成功!吐出鈔票:" + str(draw_amount))
                time.sleep(0.001)
                # 修改餘額
                self._balance -= draw_amount
                print("\t餘額爲: " + str(self._balance))
            else:
                print(multiprocessing.current_process().name\
                    + "取錢失敗!餘額不足!")
        finally:
            # 修改完成,釋放鎖
            self.lock.release()
# 定義一個函數來模擬取錢操作
def draw(account, draw_amount):
    print(account)
    # 直接調用account對象的draw()方法來執行取錢操作
    account.draw(draw_amount)

if __name__ == '__main__':
    lock = multiprocessing.RLock()
    # 創建一個賬戶
    acct = Account("1234567" , 900, lock)
    # 模擬兩個進程對同一個賬戶取錢
    multiprocessing.Process(name='甲', target=draw , args=(acct , 800)).start()
    multiprocessing.Process(name='乙', target=draw , args=(acct , 800)).start()
    multiprocessing.Process(name='丙', target=draw , args=(acct , 800)).start()

更多實例

多進程multiprocessing模塊和多線程threading模塊的使用方式很類似,在CPU密集型的程序中多線程並不能達到高效運轉的效果,爲了發揮多核CPU的優勢使用多進程更有效果

多進程調用接口

# -*- coding: utf-8 -*-
import multiprocessing
import requests
from time import ctime
import json



def syc_email(userID, userName):
    """syc_email"""

    print("Start synchronizing %s %s" % ("email", ctime()))
    parameter = {"userId":userID,"userName":userName,"enterpriseId":"10330","flag":"sended"}
    request_own = requests.put("https://xxxxxx.leadscloud.com/mail/receiveSendedAndRubbishMail", data=parameter)
    data = request_own.json()
    print(json.dumps(data, indent=4, sort_keys=True, ensure_ascii=False) )
    print("接口調用已經返回結果,本次同步結束")


dicts = {'1263':'13810078954','1294':'13810327625','1223':'18515934978','1295':'13911154792'}
threads = []
files = range(len(dicts))

for userID, userName in dicts.items():
    mp = multiprocessing.Process(target = syc_email, args = (userID, userName))
    threads.append(mp)

if __name__ == '__main__':
    for p in files:
        threads[p].start()
    for p in files:
        threads[p].join()
    print('all end: %s' % ctime())

執行結果

PS C:\Users\Administrator\Desktop> python .\multipreocess.py
Start synchronizing email Thu Mar 21 13:54:26 2019
Start synchronizing email Thu Mar 21 13:54:26 2019
Start synchronizing email Thu Mar 21 13:54:26 2019
Start synchronizing email Thu Mar 21 13:54:26 2019
{
    "code": 1,
    "data": 0,
    "msg": "成功"
}
接口調用已經返回結果,本次同步結束
{
    "code": 1,
    "data": 0,
    "msg": "成功"
}
接口調用已經返回結果,本次同步結束
{
    "code": 1,
    "data": 0,
    "msg": "成功"
}
接口調用已經返回結果,本次同步結束
{
    "code": 1,
    "data": 0,
    "msg": "成功"
}
接口調用已經返回結果,本次同步結束
all end: Thu Mar 21 14:03:22 2019

多進程啓動瀏覽器

# -*- coding: utf-8 -*-
from selenium import webdriver
from time import sleep
from time import ctime
import multiprocessing


def start_browser(browser, time):
    if browser == "chrome":
        print("starting chrome browser now! %s" % ctime())  # 控制檯打印當前時間 
        chrome_driver = webdriver.Chrome()
        chrome_driver.get("http://www.baidu.com")
        sleep(time)
        chrome_driver.quit()
    elif browser == "firefox":
        print("starting firefox browser now! %s" % ctime())  # 控制檯打印當前時間
        fire_driver = webdriver.Firefox()
        fire_driver.get("http://www.baidu.com")
        sleep(time)
        fire_driver.quit()
    else: 
        print("starting ie browser now! %s" %ctime())  # 控制檯打印當前時間
        ie_driver = webdriver.Ie()
        ie_driver.get("http://www.baidu.com")
        sleep(time)
        ie_driver.quit()


#  定義字典參數        
browser_dict = {"chrome": 3, "firefox": 4}
#  定義空List用於存儲進程
start_browser_processing = []
#  循環字典Key-Value,創建進程並加入到List中
for browser, time in browser_dict.items():
    processing_browser = multiprocessing.Process(target=start_browser, args=(browser, time))
    start_browser_processing.append(processing_browser)

if __name__ == '__main__':
    for processing_browser in range(len(browser_dict)):
        start_browser_processing[processing_browser].start()
    for processing_browser in range(len(browser_dict)):
        start_browser_processing[processing_browser].join()
    print(u"全部結束 %s" % ctime())


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章