1. 程式人生 > 實用技巧 >第四模組 第26章 網路程式設計進階

第四模組 第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): # 此處必須為run
print('%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
'''