第四模組 第26章 網路程式設計進階
1. 作業系統
雙擊QQ後,QQ程式會到CPU中去執行。 CPU同一時間只能執行一個任務,如果同時還啟動了微信,那麼如果QQ執行過程中遇到了IO,則會跳去執行微信, 執行過程中遇到IO後又會跳去執行其他程式。這樣,給使用者的體驗是QQ和微信同時在執行。如果QQ一直執行,沒有IO,則不會跳轉執行其他程式,那麼為了同時執行其他程式,則會在執行一段時間後跳轉執行其他程式。至於何時跳轉,跳轉執行那個程式,則由作業系統決定。
作業系統的兩大作用:
1. 隱藏複雜醜陋的硬體介面,提供良好的抽象介面
2. 管理排程程序, 並且把多個程序對硬體的競爭變得有序
多道技術:
單核下實現併發。 併發指的是看起來是同步執行的。 從兩個方面實現了看起來是併發的: 1. 空間複用, 即記憶體複用。2. 時間複用,即來回切換: 一是程序遇到IO阻塞要切換,這種方式可以提升CPU的執行效率;另一種方式是一個進行執行時間過長/有一個比它優先順序更高的進行,也會進行切換,這種方式會降低CPU的執行效率。每次切換前會保留當前的狀態,方便後面繼續執行。
並行:針對多核。真正意義上的同時執行。
2. 併發程式設計之多程序
2.2 開啟程序的兩種方式
# 方式一 from multiprocessing import Process import time def task(name): print('%s is running'%name) time.sleep(3) print('%s is done'%name) if __name__ == '__main__': # p = Process(target=task,kwargs={'name':'子程序1'}) # 方式一 p = Process(target=task,args=('子程序1',)) # 方式二 p.start() # 僅僅只是給作業系統發了個訊號 print('主程序') ''' 結果: 主程序 子程序1 is running 子程序1 is done ''' # 方式二 from multiprocessing import Process import time class MyProcess(Process): def __init__(self, name): super().__init__() self.name = name def run(self): # 此處必須為runprint('%s is running' % self.name) time.sleep(3) print('%s is done' % self.name) if __name__ == '__main__': p = MyProcess('子程序1') p.start()
2.3 檢視程序的pid與ppid
from multiprocessing import Process import time,os def task(): print('%s is running, parent id is <%s>'%(os.getpid(),os.getppid())) time.sleep(3) print('%s is done, parent id is <%s>'%(os.getpid(),os.getppid())) if __name__ == '__main__': p = Process(target=task,) p.start() print('主程序', os.getpid(),os.getppid()) ''' 主程序 388012 375916 384340 is running, parent id is <388012> 384340 is done, parent id is <388012> 其中, 388012是主程序id, 375916是pycharm的id '''
2.4 殭屍程序與孤兒程序
''' 殭屍程序: 子程序結束 孤兒程序: 主程序先結束 殭屍程序: 點選run後執行主程序, 在主程序的執行過程中發了系統呼叫, 開啟了子程序.主程序和子程序共用一個列印終端. 主程序幹完自己的活後會等著子程序執行完畢後再結束.目的是為了給子程序收屍. 主程序和子程序的執行是相互獨立的, 但是主程序可以檢視子程序的相關情況. 子程序執行完畢後會將自身的記憶體空間等清除感情, 但是依然會保留一些狀態資訊供主程序檢視. 子程序結束後還會保留一些資訊, 這種情況稱為殭屍程序. 所有的子程序都會經歷殭屍程序這麼一個狀態. 主程序結束後會啟用系統呼叫, 回收所有的殭屍程序. 殭屍程序是有害的, 因為會保留其pid, 但是一個程序會有一個pid, 這就會影響後面程序的建立. 殭屍程序本身沒有什麼害處, 但是如果主程序一直執行, 就會有害. 孤兒程序: 主程序較子程序先結束, 子程序就稱為了孤兒程序. 孤兒程序由init程序託管. 在linux系統中, init是所有程序的爹, 由它回收孤兒程序的殭屍程序. 孤兒程序沒有害. '''
2.5 Process物件的其他屬性和方法
# 1. join方法 # 讓主程序等待子程序執行完畢後再執行. from multiprocessing import Process import time,os def task(): print('%s is running, parent id is <%s>'%(os.getpid(),os.getppid())) time.sleep(3) print('%s is done, parent id is <%s>'%(os.getpid(),os.getppid())) if __name__ == '__main__': p = Process(target=task,) p.start() p.join() # 主程序等待子程序執行完畢 print('主程序', os.getpid(),os.getppid()) print(p.pid) ''' 結果: 200100 is running, parent id is <385428> 200100 is done, parent id is <385428> 主程序 385428 357680 200100 ''' # join下的併發 from multiprocessing import Process import time,os def task(name, n): print('%s is running'%name) time.sleep(n) if __name__ == '__main__': start_time = time.time() p1 = Process(target=task,args=('子程序1',5)) p2 = Process(target=task,args=('子程序2',3)) p3 = Process(target=task,args=('子程序3',2)) p1.start() # 只是向作業系統傳送訊號, 執行順序不一定 p2.start() p3.start() p1.join() p2.join() p3.join() # 不是序列, 依然是併發 print('主程序', (time.time()-start_time)) ''' 結果: 子程序1 is running 子程序2 is running 子程序3 is running 主程序 5.940209627151489 ''' # 簡寫 from multiprocessing import Process import time,os def task(name, n): print('%s is running'%name) time.sleep(n) if __name__ == '__main__': start_time = time.time() p1 = Process(target=task,args=('子程序1',5)) p2 = Process(target=task,args=('子程序2',3)) p3 = Process(target=task,args=('子程序3',2)) p_i = [p1, p2, p3] for p in p_i: p.start() for p in p_i: p.join() print('主程序', (time.time()-start_time)) # join下的序列 from multiprocessing import Process import time,os def task(name, n): print('%s is running'%name) time.sleep(n) if __name__ == '__main__': start_time = time.time() p1 = Process(target=task,args=('子程序1',5)) p2 = Process(target=task,args=('子程序2',3)) p3 = Process(target=task,args=('子程序3',2)) p1.start() # 只是向作業系統傳送訊號 p1.join() p2.start() p2.join() p3.start() p3.join() # 這種情況是序列 print('主程序', (time.time()-start_time)) ''' 結果: 子程序1 is running 子程序2 is running 子程序3 is running 主程序 11.631278276443481 ''' 2. is_alive判斷程序是否存活 from multiprocessing import Process import time,os def task(name, n): print('%s is running'%name) time.sleep(n) if __name__ == '__main__': p1 = Process(target=task,args=('子程序1',5)) p1.start() # 只是向作業系統傳送訊號 p1.join() print(p1.is_alive()) # False from multiprocessing import Process import time,os def task(name, n): print('%s is running'%name) time.sleep(n) if __name__ == '__main__': p1 = Process(target=task,args=('子程序1',1), name='subProcess1') # 重新命名 p1.start() p1.terminate() p1.join() # 如果沒有這行程式碼, 則為True print(p1.is_alive()) # False print(p1.name) # 檢視程序名, 如果未重新命名則為Process-1, 如果重新命名則為subProcess1
2.6 練習
1. 驗證多程序的記憶體空間之間是隔離的 from multiprocessing import Process n=100 #在windows系統中應該把全域性變數定義在if __name__ == '__main__'之上就可以了 def work(): global n n=0 print('子程序內: ',n) if __name__ == '__main__': p=Process(target=work) p.start() print('主程序內: ',n)
2. 基於多程序實現併發的套接字通訊 # 服務端 import socket from multiprocessing import Process def task(conn): while True: try: data = conn.recv(1024) # msg = input('>>>') # 多程序中不支援input功能 conn.send(data.upper()) print(data.decode('utf-8')) except ConnectionResetError: break conn.close() def server(ip, port): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind((ip,port)) server.listen(5) while True: conn, addr = server.accept() p = Process(target=task, args=(conn,)) p.start() server.close() if __name__ == '__main__': server('127.0.0.1', 8001) ''' 這種方式存在一點的弊端: 如果很多客戶端來建立連線, 則會開闢很多記憶體空間, 導致記憶體溢位. '''
# 客戶端 import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(('127.0.0.1',8001)) while True: msg = input('>>>') if not msg: continue client.send(msg.encode('utf-8')) data = client.recv(1024) print(data.decode('utf-8')) client.close()
2.6 守護程序
主程序建立子程序,然後將該程序設定成守護自己的程序,守護程序就好比崇禎皇帝身邊的老太監,崇禎皇帝已死老太監就跟著殉葬了。
關於守護程序需要強調兩點:
其一:守護程序會在主程序程式碼執行結束後就終止
其二:守護程序內無法再開啟子程序,否則丟擲異常:AssertionError: daemonic processes are not allowed to have children
如果我們有兩個任務需要併發執行,那麼開一個主程序和一個子程序分別去執行就ok了,如果子程序的任務在主程序任務結束後就沒有存在的必要了,那麼該子程序應該在開啟前就被設定成守護程序。主程序程式碼執行結束,守護程序隨即終止
''' 守護程序: 守護著主程序的程序, 主程序結束, 守護程序跟著結束. 必須在start之前設定守護程序 守護程序中不能再設定子程序, 因為這樣做可能會產生一堆孤兒程序 ''' 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.start() print('主程序') ''' 結果: 主程序 '''
# 練習 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-------") ''' main------- 456 end456 '''