python高階 6 程序
程序、程序的使用、程序注意點、程序間通訊-Queue、程序池Pool、程序與執行緒對比、資料夾拷貝器-多工
1.程序介紹
多程序本質是建立一個程序預設建立一個執行緒,任務本身是由執行緒完成的。多程序可以處理多工,但是每建立一個程序都要向系統索要執行資源,相比執行緒來說資源佔用比較多成本比較大
<1>程序的概念
程序:通俗理解一個執行的程式或者軟體,程序是作業系統資源分配的基本單位。
注意:一個程式至少有一個程序,一個程序至少有一個執行緒,多程序可以完成多工.
<2>程序的狀態
工作中,任務數往往大於cpu的核數,即一定有一些任務正在執行,而另外一些任務在等待cpu進行執行,因此導致了有不同的狀態
就緒態:執行的條件都已經滿足,正在等待cpu執行
執行態:cpu正在執行其功能
等待態:等待某些條件滿足,例如一個程式sleep了,此時就處於等待態
小結:
一個程序預設有一個執行緒,程序裡面可以建立執行緒,執行緒是依附在程序裡面的,沒有程序就沒有執行緒。
2.程序的使用
<1>多程序完成多工
匯入程序模組
import multiprocessing
<2>Process程序類的語法結構如下:
Process([group [, target [, name [, args [, kwargs]]]]])
group:指定程序組,目前只能使用None
target:執行的目標任務名
name:程序名字
args:以元組方式給執行任務傳參
kwargs:以字典方式給執行任務傳參
Process建立的例項物件的常用方法:
start():啟動子程序例項(建立子程序)
join([timeout]):是否等待子程序執行結束,或等待多少秒
terminate():不管任務是否完成,立即終止子程序
Process建立的例項物件的常用屬性:
name:當前程序的別名,預設為Process-N,N為從1開始遞增的整數
pid:當前程序的pid(程序號)
<3>多程序完成多工程式碼
import multiprocessing
import time
def run_proc():
"""子程序要執行的程式碼"""
while True:
print("----2----")
time.sleep(1)
if __name__=='__main__':
# 建立子程序
sub_process = multiprocessing.Process(target=run_proc)
# 啟動子程序
sub_process.start()
while True:
print("----1----")
time.sleep(1)
<4>獲取當前程序、當前程序pid、父程序pid(paternal pid)、殺死對應的程序
獲取當前程序
multiprocessing.current_process()
獲取當前程序的編號
multiprocessing.current_process().pid
os.getpid()
獲取父程序的編號
os.getppid()
根據程序編號殺死對應的程序
os.kill(os.getpid(), 9)
import multiprocessing
import time
import os
def work():
# 檢視當前程序
current_process = multiprocessing.current_process()
print("work:", current_process)
# 獲取當前程序的編號
print("work程序編號:", current_process.pid, os.getpid())
# 獲取父程序的編號
print("work父程序的編號:", os.getppid())
for i in range(10):
print("工作中....")
time.sleep(0.2)
# 擴充套件: 根據程序編號殺死對應的程序
os.kill(os.getpid(), 9)
if __name__ == '__main__':
# 檢視當前程序
current_process = multiprocessing.current_process()
print("main:", current_process)
# 獲取當前程序的編號
print("main程序的編號:", current_process.pid)
# 建立子程序
sub_process = multiprocessing.Process(target=work)
# 啟動程序
sub_process.start()
# 主程序執行列印資訊操作
for i in range(20):
print("我在主程序中執行...")
time.sleep(0.2)
<5>給子程序指定的函式傳遞引數
import multiprocessing
# 顯示人員資訊
def show_info(name, age):
print(name, age)
if __name__ == '__main__':
# 建立子程序
# 1. group:程序組,目前必須使用None,一般不用設定
# 2. target:執行目標函式
# 3. name: 程序名稱
# 4. args: 以元組方式給函式傳參
# 5. kwargs: 以字典方式給函式傳參
sub_process = multiprocessing.Process(target=show_info, name="myprocess", args=("古力娜扎", 18))
# 啟動程序
sub_process.start()
# sub_process = multiprocessing.Process(target=show_info, name="myprocess", kwargs={"name": "貂蟬", "age": 20})
#
# # 啟動程序
# sub_process.start()
3.程序注意點
<1>程序之間不共享全域性變數
import multiprocessing
import time
# 定義全域性變數
my_list = list()
# 寫入資料
def write_data():
for i in range(5):
my_list.append(i)
time.sleep(0.2)
print("write_data:", my_list)
# 讀取資料
def read_data():
print(my_list)
if __name__ == '__main__':
# 建立寫入資料的程序
write_process = multiprocessing.Process(target=write_data)
read_process = multiprocessing.Process(target=read_data)
write_process.start()
# 主程序等待寫入程序執行完成以後程式碼 再繼續往下執行
write_process.join()
read_process.start()
執行結果:
write_data: [0, 1, 2, 3, 4]
read_data: []
注意:
建立子程序其實是對主程序(除了main方法程式入口以外的,Windows如果建立子程序不用main程式入口會無限迴圈建立程序導致程式報錯)所有資源進行拷貝,程序之間相互獨立,訪問的全域性變數不是同一個,所以程序之間不共享全域性變數
多程序之間可能出現相同名字的全域性變數,但是不是同一個全域性變數,是不同的全域性變數,只不過名字相同而已
<2>主程序會等待所有的子程序執行完成程式再退出
import multiprocessing
import time
# 測試子程序是否執行完成以後主程序才能退出
def work():
for i in range(10):
print("工作中...")
time.sleep(0.2)
if __name__ == '__main__':
# 建立子程序
work_process = multiprocessing.Process(target=work)
work_process.start()
# 讓主程序等待1秒鐘
time.sleep(1)
print("主程序執行完成了啦")
總結: 主程序會等待所有的子程序執行完成以後程式再退出
<3>銷燬子程序的程式碼
設定守護主程序,主程序退出後子程序直接銷燬,不再執行子程序中的程式碼
子程序名.daemon = True
讓子程序直接銷燬,表示終止執行, 主程序退出之前,把所有的子程序直接銷燬就可以了
子程序名.terminate()
import multiprocessing
import time
# 測試子程序是否執行完成以後主程序才能退出
def work():
for i in range(10):
print("工作中...")
time.sleep(0.2)
if __name__ == '__main__':
# 建立子程序
work_process = multiprocessing.Process(target=work)
# 設定守護主程序,主程序退出後子程序直接銷燬,不再執行子程序中的程式碼
# work_process.daemon = True
work_process.start()
# 讓主程序等待1秒鐘
time.sleep(1)
print("主程序執行完成了啦")
# 讓子程序直接銷燬,表示終止執行, 主程序退出之前,把所有的子程序直接銷燬就可以了
work_process.terminate()
# 總結: 主程序會等待所有的子程序執行完成以後程式再退出
小結:
程序之間不共享全域性變數
主程序會等待所有的子程序執行完成程式再退出
4.程序間通訊-Queue
<1>Queue的使用
可以使用multiprocessing模組的Queue實現多程序之間的資料傳遞,Queue本身是一個訊息佇列程式
首先用一個小例項來演示一下Queue的工作原理:
import multiprocessing
import time
if __name__ == '__main__':
# 建立訊息佇列, 3:表示佇列中最大訊息個數
queue = multiprocessing.Queue(3)
# 放入資料
queue.put(1)
queue.put("hello")
queue.put([3,5])
# 總結: 佇列可以放入任意資料型別
# 提示: 如果佇列滿了,需要等待佇列有空閒位置才能放入資料,否則一直等待
# queue.put((5,6))
# 提示: 如果佇列滿了,不等待佇列有空閒位置,如果放入不成功直接崩潰
# queue.put_nowait((5,6))
# 建議: 向佇列放入資料統一使用put
# 檢視佇列是否滿了
# print(queue.full())
# 注意點:queue.empty()判斷佇列是否空了不可靠
# 檢視佇列是否空了
# print(queue.empty())
# 解決辦法: 1. 加延時操作 2. 使用判斷佇列的個數,不使用empty
# time.sleep(0.01)
if queue.qsize() == 0:
print("佇列為空")
else:
print("佇列不為空")
# 獲取佇列的個數
size = queue.qsize()
print(size)
# 獲取資料
value = queue.get()
print(value)
# 獲取佇列的個數
size = queue.qsize()
print(size)
# 獲取資料
value = queue.get()
print(value)
# 獲取資料
value = queue.get()
print(value)
# 獲取佇列的個數
size = queue.qsize()
print(size)
# 提示:如果佇列空了,再取值需要等待,只有佇列有值以後才能獲取佇列中資料
# value = queue.get()
# print(value)
# queue.get_nowait()提示: 如果佇列空了 ,不需要等待佇列有值,但是如果取值的時候發現佇列空了直接崩潰
# 建議大家: 向佇列取值使用get
# value = queue.get_nowait()
# print(value)
說明:
初始化Queue()物件時(例如:q=Queue()),若括號中沒有指定最大可接收的訊息數量,或數量為負值,那麼就代表可接受的訊息數量沒有上限(直到記憶體的盡頭);
Queue.qsize():返回當前佇列包含的訊息數量;
Queue.empty():如果佇列為空,返回True,反之False , 注意這個操作是不可靠的。
Queue.full():如果佇列滿了,返回True,反之False;
Queue.get([block[, timeout]]):獲取佇列中的一條訊息,然後將其從列隊中移除,block預設值為True;
1)如果block(阻塞)使用預設值,且沒有設定timeout(暫時休息)(單位秒),訊息列隊如果為空,此時程式將被阻塞(停在讀取狀態),直到從訊息列隊讀到訊息為止,如果設定了timeout,則會等待timeout秒,若還沒讀取到任何訊息,則丟擲"Queue.Empty"異常;
2)如果block值為False,訊息列隊如果為空,則會立刻丟擲"Queue.Empty"異常;
Queue.get_nowait():相當Queue.get(False);
Queue.put(item,[block[, timeout]]):將item訊息寫入佇列,block預設值為True;
1)如果block使用預設值,且沒有設定timeout(單位秒),訊息列隊如果已經沒有空間可寫入,此時程式將被阻塞(停在寫入狀態),直到從訊息列隊騰出空間為止,如果設定了timeout,則會等待timeout秒,若還沒空間,則丟擲"Queue.Full"異常;
2)如果block值為False,訊息列隊如果沒有空間可寫入,則會立刻丟擲"Queue.Full"異常;
Queue.put_nowait(item):相當Queue.put(item, False);
<2>訊息佇列Queue完成程序間通訊的演練
我們以Queue為例,在父程序中建立兩個子程序,一個往Queue裡寫資料,一個從Queue裡讀資料:
import multiprocessing
import time
# 寫入資料
def write_data(queue):
for i in range(10):
if queue.full():
print("佇列滿了")
break
queue.put(i)
time.sleep(0.2)
print(i)
# 讀取資料
def read_data(queue):
while True:
# 加入資料從佇列取完了,那麼跳出迴圈
if queue.qsize() == 0:
print("佇列空了")
break
value = queue.get()
print(value)
if __name__ == '__main__':
# 建立訊息佇列
queue = multiprocessing.Queue(5)
# 建立寫入資料的程序
write_process = multiprocessing.Process(target=write_data, args=(queue,))
# 建立讀取資料的程序
read_process = multiprocessing.Process(target=read_data, args=(queue,))
# 啟動程序
write_process.start()
# 主程序等待寫入程序執行完成以後程式碼再繼續往下執行
write_process.join()
read_process.start()
小結:
從佇列取值使用get方法,向佇列放入值使用put方法
訊息佇列判斷佇列是否為空不可靠,可以使用延時和根據個數進行判斷
5.程序池Pool
程序池的好處:
根據任務自動建立程序
合理利用指定程序完成多工
<1>程序池的概念
池子裡面放的是程序,程序池會根據任務執行情況自動建立程序,而且儘量少建立程序,合理利用程序池中的程序完成多工
(根據任務執行情況自動建立程序,並不是一開始就建立最大數量的程序)
當需要建立的子程序數量不多時,可以直接利用multiprocessing中的Process動態成生多個程序,但如果是上百甚至上千個目標,手動的去建立程序的工作量巨大,此時就可以用到multiprocessing模組提供的Pool方法。
初始化Pool時,可以指定一個最大程序數,當有新的請求提交到Pool中時,如果池還沒有滿,那麼就會建立一個新的程序用來執行該請求;但如果池中的程序數已經達到指定的最大值,那麼該請求就會等待,直到池中有程序結束,才會用之前的程序來執行新的任務.
<2>程序池同步執行任務
程序池同步執行任務表示程序池中的程序在執行任務的時候一個執行完成另外一個才能執行,如果沒有執行完會等待上一個程序執行
程序池同步例項程式碼
import multiprocessing
import time
# 拷貝任務
def work():
print("複製中...", multiprocessing.current_process().pid)
time.sleep(0.5)
if __name__ == '__main__':
# 建立程序池
# 3:程序池中程序的最大個數,如果不設定,預設最大程序個數為電腦CPU的核數
pool = multiprocessing.Pool(3)
# 模擬大批量的任務,讓程序池去執行
for i in range(5):
# 迴圈讓程序池執行對應的work任務
# 同步執行任務,一個任務執行完成以後另外一個任務才能執行
pool.apply(work)
<3>程序池非同步執行任務
程序池非同步執行任務表示程序池中的程序同時執行任務,程序之間不會等待
非同步執行,任務執行不會等待,多個任務一起執行
程序池名.apply_async(任務名)
# 關閉程序池,意思告訴主程序以後不會有新的任務新增進來
程序池名.close()
# 主程序等待程序池執行完成以後程式再退出
程序池名.join()
程序池非同步例項程式碼
# 程序池:池子裡面放的程序,程序池會根據任務執行情況自動建立程序,而且儘量少建立程序,合理利用程序池中的程序完成多工
import multiprocessing
import time
# 拷貝任務
def work():
print("複製中...", multiprocessing.current_process().pid)
# 獲取當前程序的守護狀態
# 提示:使用程序池建立的程序是守護主程序的狀態true,預設自己通過Process建立的程序不是守護主程序的狀態false
# print(multiprocessing.current_process().daemon)
time.sleep(0.5)
if __name__ == '__main__':
# 建立程序池
# 3:程序池中程序的最大個數
pool = multiprocessing.Pool(3)
# 模擬大批量的任務,讓程序池去執行
for i in range(5):
# 迴圈讓程序池執行對應的work任務
# 同步執行任務,一個任務執行完成以後另外一個任務才能執行
# pool.apply(work)
# 非同步執行,任務執行不會等待,多個任務一起執行
pool.apply_async(work)
# 關閉程序池,意思告訴主程序以後不會有新的任務新增進來
pool.close()
# 主程序等待程序池執行完成以後程式再退出
pool.join()
小結:
multiprocessing.Pool常用函式解析:
同步執行:apply(func[, args[, kwds]]): 阻塞方式呼叫函式,args表示以元組方式給函式傳參,kwds表示以字典方式給函式傳參
非同步執行:apply_async(func[, args[, kwds]]) :使用非阻塞方式呼叫函式,args表示以元組方式給函式傳參,kwds表示以字典方式給函式傳參
close():關閉Pool,使其不再接受新的任務;
terminate():不管任務是否完成,立即終止;
join():主程序阻塞,等待子程序的退出, 必須在close或terminate之後使用;
6.程序、執行緒對比
<1>功能對比
程序,能夠完成多工,比如 在一臺電腦上能夠同時執行多個QQ
執行緒,能夠完成多工,比如 一個QQ中的多個聊天視窗
<2>定義對比
程序是系統進行資源分配基本單位,每啟動一個程序作業系統都需要為其分配執行資源。
執行緒是CPU排程基本單位,是執行程式中的一個執行分支。
總結:程序是作業系統資源分配的基本單位,執行緒是CPU排程的基本單位
<3>關係對比
執行緒是依附在程序裡面的,沒有程序就沒有執行緒
一個程序預設提供一條執行緒,程序可以建立多個執行緒
<4>區別
程序之間不共享全域性變數
執行緒之間共享全域性變數,但是要注意資源競爭的問題,解決辦法: 互斥鎖或者執行緒同步
建立程序的資源開銷要比建立執行緒的資源開銷要大
程序是作業系統資源分配的基本單位,執行緒是CPU排程的基本單位
執行緒不能夠獨立執行,必須依存在程序中
多程序開發比單程序多執行緒開發穩定性要強
<5>優缺點
多程序:
優點:可以用多核
缺點:資源開銷大
多執行緒:
優點:資源開銷小
缺點:不能使用多核
7.資料夾拷貝器-多工
<1>功能要求
使用程序池完成多工資料夾的拷貝
import os
import shutil
import multiprocessing
# import time
# 檔案拷貝任務
def copy_work(src_dir, dst_dir, file_name):
# 檢視程序物件
pid = multiprocessing.current_process().pid
print(pid)
# 拼接原始檔的路徑
src_file_path = src_dir + "/" + file_name
# 拼接目標檔案的路徑
dst_file_path = dst_dir + "/" + file_name
with open(dst_file_path, "wb") as dst_file:
# 打原始檔讀取檔案中的資料
with open(src_file_path, "rb") as src_file:
while True:
# 讀取資料
src_file_data = src_file.read(1024)
if src_file_data:
# 寫入到目標檔案裡面
dst_file.write(src_file_data)
else:
break
# time.sleep(0.5)
if __name__ == '__main__':
# 源目錄
src_dir = "test"
# 目標目錄
dst_dir = "/home/python/Desktop/test"
# 判斷資料夾是否存在
if os.path.exists(dst_dir):
# 存在則刪除資料夾及資料夾裡面的所有檔案
shutil.rmtree(dst_dir)
# 建立目標資料夾
os.mkdir(dst_dir)
# 獲取源目錄裡面檔案的列表
file_name_list = os.listdir(src_dir)
# 建立程序池
pool = multiprocessing.Pool(3)
# 遍歷檔案裡面獲取檔名
for file_name in file_name_list:
# 使用程序池執行拷貝任務,使用*args和**kwagrs傳引數!此處選擇元組
pool.apply_async(copy_work, (src_dir, dst_dir, file_name))
# 關閉程序池
pool.close()
# 主程序等待程序池執行完成以後程式再退出
pool.join()
小結:
程序池在執行任務的時候會盡量少建立程序,合理利用現有程序完成多工,這樣可以減少資源開銷