1. 程式人生 > >20181228 併發程式設計初步

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('主程序')