使用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())