20181228 併發程式設計初步
併發程式設計
併發即同時發生,併發程式設計指程式中的多個任務同時被處理,其基於多道技術。
併發與並行
併發指的是多個事件同時發生,也稱為偽並行(併發事實上是交替進行,如果切換速度足夠快,那麼就可以讓人覺得是在同時發生)
並行指的是多個事件同時進行
阻塞與非阻塞
阻塞:程式遇到I/O操作或是sleep,導致後續程式碼不能被CPU執行的狀態。
非阻塞:程式碼正常被CPU執行。
多道技術:
空間複用:記憶體分成幾個部分,不同部分執行不同程式,彼此之間獨立。 時間複用:當一個程式執行非計算操作(如I/O,等待使用者輸入;或作業系統強制切換),將CPU切到另一個程式進行計算操作。
程序的三種狀態:就緒態,執行態,阻塞態
多道技術會在程序執行時間過長或遇到I/O時自動切換到其他程序,原程序被剝奪CPU執行權,需要重新進入就緒態後才能等待執行。
程序:正在執行的程式就是程序,是一種抽象的概念。 程序依賴於作業系統,一個程序代表著一份資源(記憶體等),程序間記憶體相互獨立,一個程式可以有多個程序。
# 子程序中的資料修改,不會影響父程序,即子程序和父程序之間相互獨立(子程序在執行前會拷貝父程序的環境,所以在子程序執行前要初始化好父程序的環境)
import time
a = 100
def task():
global a
a = 0 # 子程序中的全域性變數a已經變為0,但是父程序中的全域性變數a,子程序無權修改。
print("子程序",a)
if __name__ == '__main__':
p = Process(target=task)
p.start()
time.sleep(1) # 注意此處的時間停頓,此處引出join函式(子程序全部結束再執行父程序),詳見最後。
print("主程式",a)
輸出結果為:
子程序 0
主程式 100
與上面的做一下對比:
兩次結果的差異在於:子程序需要載入才能執行,這時候CPU可以進行主程式的其他內容操作。如果主程式睡眠,那麼睡眠時間就夠子程式執行完了。所以在輸出順序上存在差異。
import time
a = 100
def task():
global a
a = 0
print("子程序",a)
if __name__ == '__main__':
p = Process(target=task)
p.start()
print("主程式",a)
輸出結果為:
主程式 100
子程序 0
PID與PPID
PID:作業系統給每個程序編號,這個編號就是PID
PPID:當程序A開啟了另一個程序B,A稱為B的父程序,B稱為A的子程序,作業系統可以看做是所有主程序的父程序。
import os
while True:
print("haha")
print("self",os.getpid()) # 獲取self的程序ID,ID號由作業系統分配
print("parent",os.getppid()) # 第一個p指的是parent,即父程序的ID
break
# 每次的ID號都會不同,pid是當前執行程式,如果這段程式碼放到pycharm中執行,ppid就是pycharm的編號,如果這段程式碼放到cmd中執行,ppid就是cmd的編號。
孤兒程序和殭屍程序:
孤兒程序:父程序已經終結,但子程序仍在執行,無害,作業系統會負責回收處理等操作。
殭屍程序:子程序執行完畢,殘留一些資訊(如程序id等)。但父程序沒有處理殘留資訊,
導致殘留資訊佔用記憶體,有害。
瞭解
作業系統的兩個核心作用: 1、遮蔽了複雜繁瑣的硬體介面,為程式提供了簡單易用的系統介面 2、將應用程式對硬體資源的競爭變成有序的使用。
作業系統與普通應用程式的區別: 1、作業系統不能輕易被使用者修改 2、作業系統原始碼量巨大,僅核心通常就在五百萬行以上 3、長壽,一旦完成不會輕易重寫。
python中開啟子程序的兩種方式
方式1:
例項化Process類
from multiprocessing import Process
import os
def task():
print("self",os.getpid()) # 自己的id
print("parent",os.getppid()) # 父程序的id,此處的父程序是下面的p的那個程序(由p啟動)
print("task run")
# win下建立子程序時,子程序會將父程序的程式碼載入一遍,導致重複建立子程序
# 所以一定要將建立子程序的程式碼放到main的下面。
if __name__ == '__main__':
print("self",os.getpid()) # 當前執行內容的id
print("parent",os.getppid()) # 通過pycharm執行,所以此處的父程序id是pycharm的id
p = Process(target=task, name="子程序") # 主程序啟動子程序,目標子程序為task(注意此處僅為函式名)。建立一個表示程序的物件,並不是真正的建立程序,Process是一個類,他還有很多引數和方法,具體可以ctrl點進去檢視。
p.start() # 向作業系統傳送請求,作業系統會申請記憶體空間,然後把父程序的資料拷貝給子程序,作為子程序的初始狀態(但你爹永遠是你爹(父程序),接產大夫(作業系統)不會成為隔壁老王)
方式2:
繼承Process類 並覆蓋run方法,子程序啟動後會自動執行run方法。
這種方法可以自定義程序的屬性和行為,拓展物件的功能。
class MyProcess(Process): # 繼承Process類
def __init__(self,url):
super().__init__()
self.url = url
def run(self): # 覆蓋run方法
print("正在下載檔案。。。",self.url)
print("runrunrun")
if __name__ == '__main__':
p = MyProcess("www.baidu.com")
p.start() # 例項化了Process物件,start自動呼叫run。
需要注意的是 在windows下 開啟子程序必須放到__main__
下面,因為windows在開啟子程序時會重新載入所有的程式碼造成遞迴
join函式
呼叫start函式後的操作就由作業系統來玩了,至於何時開啟程序,程序何時執行,何時結束都與應用程式無關,所以當前程序會繼續往下執行,join函式可以讓父程序等待子程序結束後再執行。(即提高子程序優先順序)
案例1:
from multiprocessing import Process
import time
x=1000
def task():
time.sleep(3)
global x
x=0
print('兒子死啦',x)
if __name__ == '__main__':
p=Process(target=task)
p.start()
p.join() # 讓父親在原地等,子程序執行完畢才會執行下面的程式碼。
print(x)
案例2:
from multiprocessing import Process
import time
def task(num):
print("我是%s程序" %num)
time.sleep(2) # 排隊睡,都是就緒態,輸出結果可能是亂序態
print("我是%s程序" %num)
if __name__ == '__main__':
start_time = time.time()
ps = []
for i in range(5):
p = Process(target=task,args=(i,)) # 注意此處傳參方式!元組形式
p.start()
ps.append(p)
for p in ps:
p.join()
print("over")
print(time.time()-start_time)
輸出結果:
我是0程序
我是1程序
我是2程序
我是0程序
我是3程序
我是1程序
我是4程序
我是2程序
我是3程序
我是4程序
over
5.9241626262664795 #正常應該是2秒多,我電腦太垃圾了,所以卡的接近6秒。
也可以寫成:
from multiprocessing import Process
import time
def task(num):
print("我是%s程序" %num)
time.sleep(2) # 排隊睡,都是就緒態,輸出結果可能是亂序態
print("我是%s程序" %num)
if __name__ == '__main__':
start_time = time.time()
for i in range(5):
p = Process(target=task,args=(i,))
p.start()
p.join() # 提高子程序優先順序,CPU優先處理子程序,父程序放到後面
print("over")
print(time.time()-start_time)
輸出結果為:
我是0程序
我是1程序
我是2程序
我是0程序
我是3程序
我是1程序
我是4程序
我是2程序
我是3程序
我是4程序
over
5.880282878875732
如果改寫成這樣:
from multiprocessing import Process
import time
def task(num):
print("我是%s程序" %num)
time.sleep(2) # 排隊睡,都是就緒態,輸出結果可能是亂序態
print("我是%s程序" %num)
if __name__ == '__main__':
start_time = time.time()
for i in range(5):
p = Process(target=task,args=(i,))
p.start()
p.join() # 提高子程序優先順序,CPU優先處理子程序,父程序放到後面,此處為只有當前子程序結束後,才會繼續迴圈。可以理解為for迴圈還是屬於父程序的內容。
print("over")
print(time.time()-start_time)
輸出結果為:
我是0程序
我是0程序
我是1程序
我是1程序
我是2程序
我是2程序
我是3程序
我是3程序
我是4程序
我是4程序
over
14.754556894302368 #正常應該是10秒多一點,我這戴爾垃圾太卡,果然是傻多戴。
Process物件常用屬性
from multiprocessing import Process
def task(n):
print('%s is runing' %n)
time.sleep(n)
if __name__ == '__main__':
start_time=time.time()
p1=Process(target=task,args=(1,),name='任務1') # 注意傳參方式
p1.start()
print(p1.pid) # 獲取程序編號
print(p1.name) # 獲取程序屬性
p1.terminate() # 結束程序
p1.join() # 提高程序優先順序
print(p1.is_alive()) # 判斷程序是否存在
print('主程序')