(三)多程序之守護程序與互斥鎖
一、守護程序
1,主程序建立子程序,然後將該程序設定成守護自己的程序,守護程序就好比皇帝身邊的老太監,皇帝已死老太監就跟著殉葬了。
關於守護程序需要強調兩點:
其一:守護程序會在主程序程式碼執行結束後就終止。
其二:守護程序內無法再開啟子程序,否則丟擲異常:AssertionError: daemonic processes are not allowed to have children
如果我們有兩個任務需要併發執行,那麼開一個主程序和一個子程序分別去執行就ok了,如果子程序的任務在主程序任務結束後就沒有存在的必要了,那麼該子程序應該在開啟前就被設定成守護程序。主程序程式碼執行結束,守護程序隨即終止。
注意:程序之間是互相獨立的,主程序程式碼執行結束,守護程序隨即終止。
from multiprocessing import Process import time def task(name): print("%s is running." % name) time.sleep(2) if __name__ == "__main__": p = Process(target=task,args=("子程序1",)) p.daemon = True # 守護程序一定是要在開啟之前設定,設定p為守護程序,禁止p建立子程序,並且父程序程式碼執行結束,p即終止執行。p.start() print("主") # 只要終端打印出這行內容,主程序就算執行完了,那麼守護程序 p 也就跟著結束掉了。
2,練習題:思考下列程式碼的執行結果有可能有哪些情況?why?
from multiprocessing import Process import time def foo(): print(123) time.sleep(1) print("end123") def bar(): print(456) time.sleep(3) print("end456") if __name__== '__main__': p1 = Process(target=foo) p2 = Process(target=bar) p1.daemon = True p1.start() p2.start() print("main-------")
from multiprocessing import Process import time def foo(): print(123) time.sleep(1) print("end123") def bar(): print(456) time.sleep(3) print("end456") if __name__ == '__main__': p1 = Process(target=foo) p2 = Process(target=bar) p1.daemon = True # 把 p1 設定成守護程序,main------ 打印出來之後,它就跟著沒了 p1.start() p2.start() print("main-------") # 這行打印出來,主程序就執行完了 # 機器效能高的情況下,會出現,p2 的訊號的傳送過程中,p1 已經立馬開啟起來了,會瞬間列印123,然後會列印main----,然後p2起來 # 但是隻要看到main------,絕對不會看到 end123。 """ main------- 456 end456 """result
二、互斥鎖
程序之間資料不共享,但是共享同一套檔案系統,所以訪問同一個檔案,或同一個列印終端,是沒有問題的,而共享帶來的是競爭,競爭帶來的結果就是錯亂,如下:
# 併發執行,效率高,但競爭同一列印終端,帶來了列印錯亂。 from multiprocessing import Process import time def task(name): print("%s 1" % name) time.sleep(1) print("%s 2" % name) time.sleep(1) print("%s 3" % name) if __name__ == "__main__": for i in range(3): p = Process(target=task,args=("程序%s" % i,)) p.start() """ 結果是錯亂,三個程序是共享同一個輸出終端的, 共享帶來的就是競爭,誰搶到了,誰就列印,這就用到了互斥鎖。 程序0 1 程序2 1 程序1 1 程序0 2 程序1 2 程序2 2 程序0 3 程序1 3 程序2 3 """
如何控制,就是加鎖處理。而互斥鎖的意思就是互相排斥,如果把多個程序比喻為多個人,互斥鎖的工作原理就是多個人都要去爭搶同一個資源:衛生間,一個人搶到衛生間後上一把鎖,其他人都要等著,等到這個完成任務後釋放鎖,其他人才有可能有一個搶到......所以互斥鎖的原理,就是把併發改成序列,降低了效率,但保證了資料安全不錯亂。
# 由併發變成了序列,犧牲了執行效率,但避免了競爭 from multiprocessing import Process,Lock import time def task(name,mutex): mutex.acquire() # 加鎖 print("%s 1" % name) time.sleep(1) print("%s 2" % name) time.sleep(1) print("%s 3" % name) mutex.release() # 把鎖釋放掉 if __name__ == "__main__": mutex = Lock() for i in range(3): p = Process(target=task,args=("程序%s" % i,mutex)) # 確保所有的程序用的是同一把鎖,就把這個鎖傳遞給子程序。 p.start() """ 雖然效率降低了,但是避免了競爭,錯亂 程序0 1 程序0 2 程序0 3 程序1 1 程序1 2 程序1 3 程序2 1 程序2 2 程序2 3 """
三、模擬搶票練習
多個程序共享同一檔案,我們可以把檔案當資料庫,用多個程序模擬多個人執行搶票任務。
# 檔案db.txt的內容為:{"count":1} # 注意一定要用雙引號,不然json無法識別 from multiprocessing import Process import json import time def search(name): """查票""" time.sleep(1) # 模擬網路延遲 dic = json.load(open("db.txt","r",encoding="utf-8")) print("<%s>檢視到剩餘票數<%s>" % (name,dic["count"])) def get(name): """購票""" time.sleep(1) dic = json.load(open("db.txt", "r", encoding="utf-8")) if dic["count"] > 0: dic["count"] -= 1 time.sleep(3) json.dump(dic,open("db.txt","w",encoding="utf-8")) print("<%s>購票成功" % name) def task(name): search(name) get(name) if __name__ == "__main__": for i in range(1,11): p = Process(target=task,args=("路人%s" % i,)) p.start() # 併發執行,效率高,但競爭同一檔案,資料寫入錯亂,只有一張票,卻成功賣給了10個人 """ <路人1>檢視到剩餘票數<1> <路人2>檢視到剩餘票數<1> <路人3>檢視到剩餘票數<1> <路人4>檢視到剩餘票數<1> <路人5>檢視到剩餘票數<1> <路人6>檢視到剩餘票數<1> <路人7>檢視到剩餘票數<1> <路人8>檢視到剩餘票數<1> <路人9>檢視到剩餘票數<1> <路人10>檢視到剩餘票數<1> <路人1>購票成功 <路人2>購票成功 <路人3>購票成功 <路人4>購票成功 <路人5>購票成功 <路人6>購票成功 <路人7>購票成功 <路人8>購票成功 <路人9>購票成功 <路人10>購票成功 """併發執行,效率高,但競爭同一檔案,資料寫入錯亂,只有一張票,賣成功給了10個人。
# 加鎖處理:購票行為由併發變成了序列,犧牲了執行效率,但保證了資料安全 from multiprocessing import Process,Lock import json import time def search(name): """查票""" time.sleep(1) # 模擬網路延遲 dic = json.load(open("db.txt","r",encoding="utf-8")) print("<%s>檢視到剩餘票數<%s>" % (name,dic["count"])) def get(name): """購票""" time.sleep(1) dic = json.load(open("db.txt", "r", encoding="utf-8")) if dic["count"] > 0: dic["count"] -= 1 time.sleep(3) json.dump(dic,open("db.txt","w",encoding="utf-8")) print("<%s>購票成功" % name) def task(name,mutex): search(name) mutex.acquire() get(name) mutex.release() if __name__ == "__main__": mutex = Lock() for i in range(1,11): p = Process(target=task,args=("路人%s" % i,mutex)) p.start() """ <路人1>檢視到剩餘票數<1> <路人2>檢視到剩餘票數<1> <路人3>檢視到剩餘票數<1> <路人4>檢視到剩餘票數<1> <路人5>檢視到剩餘票數<1> <路人6>檢視到剩餘票數<1> <路人7>檢視到剩餘票數<1> <路人8>檢視到剩餘票數<1> <路人9>檢視到剩餘票數<1> <路人10>檢視到剩餘票數<1> <路人1>購票成功 Process finished with exit code 0 """加鎖處理:購票行為由併發變成了序列,犧牲了執行效率,但保證了資料的安全
四、互斥鎖與join
使用 join 可以將併發變成序列,互斥鎖的原理也是將併發變成穿行,那我們直接使用 join 就可以了啊,為何還要互斥鎖,那我們就來試一下:
# 互斥鎖與 join from multiprocessing import Process import json import time def search(name): """查票""" time.sleep(1) # 模擬網路延遲 dic = json.load(open("db.txt","r",encoding="utf-8")) print("<%s>檢視到剩餘票數<%s>" % (name,dic["count"])) def get(name): """購票""" time.sleep(1) dic = json.load(open("db.txt", "r", encoding="utf-8")) if dic["count"] > 0: dic["count"] -= 1 time.sleep(3) json.dump(dic,open("db.txt","w",encoding="utf-8")) print("<%s>購票成功" % name) else: print("<%s>購票失敗" % name) def task(name): search(name) get(name) if __name__ == "__main__": for i in range(1,11): p = Process(target=task,args=("路人%s" % i,)) p.start() p.join() # 程序按順序一個個執行 """ 這種方式連檢視票都變成序列的了,而我們只想要購票是序列的。 <路人1>檢視到剩餘票數<1> <路人1>購票成功 <路人2>檢視到剩餘票數<0> <路人2>購票失敗 <路人3>檢視到剩餘票數<0> <路人3>購票失敗 <路人4>檢視到剩餘票數<0> <路人4>購票失敗 <路人5>檢視到剩餘票數<0> <路人5>購票失敗 <路人6>檢視到剩餘票數<0> <路人6>購票失敗 <路人7>檢視到剩餘票數<0> <路人7>購票失敗 <路人8>檢視到剩餘票數<0> <路人8>購票失敗 <路人9>檢視到剩餘票數<0> <路人9>購票失敗 <路人10>檢視到剩餘票數<0> <路人10>購票失敗 """
發現使用 join 將併發改成穿行,確實能保證資料安全,但問題是連查票操作也變成只能一個一個人去查了,很明顯大家查票時應該是併發地去查詢而無需考慮資料準確與否,此時 join 與互斥鎖的區別就顯而易見了,join是將一個任務整體序列,而互斥鎖的好處則是可以將一個任務中的某一段程式碼序列,比如只讓 task 函式中的 get 任務序列:
def task(name,): search(name) # 併發執行 mutex.acquire() get(name) # 序列執行 mutex.release()
總結:
# 加鎖可以保證多個程序修改同一塊資料時,同一時間只能有一個任務可以進行修改,即序列的修改,沒錯,速度是慢了,但犧牲了速度卻保證了資料安全。 雖然可以用檔案共享資料實現程序間通訊,但問題是: 1.效率低(共享資料基於檔案,而檔案是硬碟上的資料) 2.需要自己加鎖處理 # 因此我們最好找尋一種解決方案能夠兼顧:1、效率高(多個程序共享一塊記憶體的資料)2、幫我們處理好鎖問題。這就是mutiprocessing模組為我們提供的基於訊息的IPC通訊機制:佇列和管道。 1 佇列和管道都是將資料存放於記憶體中 2 佇列又是基於(管道+鎖)實現的,可以讓我們從複雜的鎖問題中解脫出來, 我們應該儘量避免使用共享資料,儘可能使用訊息傳遞和佇列,避免處理複雜的同步和鎖問題,而且在程序數目增多時,往往可以獲得更好的可獲展性。Within daemon can no longer turn a child process