1. 程式人生 > >併發程式設計之程序,多路複用,multiprocess模組

併發程式設計之程序,多路複用,multiprocess模組

併發
1. 背景知識
2. 什麼是程序
3. 程序排程
4. 併發與並行
5 同步\非同步\阻塞\非阻塞(重點)
6.multiprocess模組
7.殭屍程序與孤兒程序

1.背景知識

一作業系統的作用:
1:隱藏醜陋複雜的硬體介面,提供良好的抽象介面
2:管理、排程程序,並且將多個程序對硬體的競爭變得有序

二 多道技術:
1.產生背景:針對單核,實現併發
ps:現在的主機一般是多核,那麼每個核都會利用多道技術有4個cpu,運行於cpu1的某個程式遇到io阻塞,會等到io結束再重新排程,
會被排程到4個cpu中的任意一個,具體由作業系統排程演算法決定。

2.空間上的複用:如記憶體中同時有多道程式

3.時間上的複用:複用一個cpu的時間片
強調:遇到io切,佔用cpu時間過長也切,核心在於切之前將程序的狀態儲存下來,這樣才能保證下次切換回來時,能基於上次切走的位置繼續執行


2. 什麼是程序

狹義定義:程序是正在執行的程式的例項(an instance of a computer program that is being executed)。
廣義定義:程序是一個具有一定獨立功能的程式關於某個資料集合的一次執行活動。它是作業系統動態執行的基本單元,在傳統的作業系統中,程序既是基本的分配單元,也是基本的執行單元。

程序的概念:
第一,程序是一個實體。每一個程序都有它自己的地址空間,一般情況下,包括文字區域(text region)(python的檔案)、資料區域(data region)(python檔案中定義的一些變數資料)和堆疊(stack region)。
文字區域儲存處理器執行的程式碼;資料區域儲存變數和程序執行期間使用的動態分配的記憶體;堆疊區域儲存著活動過程呼叫的指令和本地變數。

第二,程序是一個“執行中的程式”。程式是一個沒有生命的實體,只有處理器賦予程式生命時(作業系統執行之),它才能成為一個活動的實體,我們稱其為程序。[3]
程序是作業系統中最基本、重要的概念。是多道程式系統出現後,為了刻畫系統內部出現的動態情況,描述系統內部各道程式的活動規律引進的一個概念,所有多道程式設計作業系統都建立在程序的基礎上。

程序的特徵
動態性:程序的實質是程式在多道程式系統中的一次執行過程,程序是動態產生,動態消亡的。
併發性:任何程序都可以同其他程序一起併發執行
獨立性:程序是一個能獨立執行的基本單位,同時也是系統分配資源和排程的獨立單位;
非同步性:由於程序間的相互制約,使程序具有執行的間斷性,即程序按各自獨立的、不可預知的速度向前推進
結構特徵:程序由程式、資料和程序控制塊三部分組成。

多個不同的程序可以包含相同的程式:一個程式在不同的資料集裡就構成不同的程序,能得到不同的結果;但是執行過程中,程式不能發生改變。

程式和程序的區別

程式是指令和資料的有序集合,其本身沒有任何執行的含義,是一個靜態的概念。
而程序是程式在處理機上的一次執行過程,它是一個動態的概念。
程式可以作為一種軟體資料長期存在,而程序是有一定生命期的。
程式是永久的,程序是暫時的。

3. 程序排程
1.先來先服務(FCFS)排程演算法是一種最簡單的排程演算法,該演算法既可用於作業排程,也可用於程序排程。FCFS演算法比較有利於長作業(程序),而不利於短作業(程序)
2.短作業(程序)優先排程演算法(SJ/PF)是指對短作業或短程序優先排程的演算法,該演算法既可用於作業排程,也可用於程序排程。但其對長作業不利;不能保證緊迫性作業(程序)被及時處理;作業的長短只是被估算出來的
3.時間片輪轉(Round Robin,RR)法的基本思路是讓每個程序在就緒佇列中的等待時間與享受服務的時間成比例。在時間片輪轉法中,需要將CPU的處理時間分成固定大小的時間片,例如,幾十毫秒至幾百毫秒
4.多級反饋佇列排程演算法,不必事先知道各種程序所需的執行時間,而且還可以滿足各種型別程序的需要,因而它是目前被公認的一種較好的程序排程演算法。


4. 併發與並行
無論是並行還是併發,在使用者看來都是'同時'執行的,不管是程序還是執行緒,都只是一個任務而已,真是幹活的是cpu,cpu來做這些任務,而一個cpu同一時刻只能執行一個任務
併發:是偽並行,即看起來是同時執行。單個cpu+多道技術就可以實現併發,(並行也屬於併發)
並行:同時執行,只有具備多個cpu才能實現並行

多道技術概念回顧:記憶體中同時存入多道(多個)程式,cpu從一個程序快速切換到另外一個,使每個程序各自執行幾十或幾百毫秒,
這樣,雖然在某一個瞬間,一個cpu只能執行一個任務,但在1秒內,cpu卻可以執行多個程序,這就給人產生了並行的錯覺,即偽並行,以此來區分多處理器作業系統的真正硬體並行(多個cpu共享同一個實體記憶體)


5.同步\非同步\阻塞\非阻塞(重點)
1.程序狀態介紹
(1)就緒(Ready)狀態當程序已分配到除CPU以外的所有必要的資源,只要獲得處理機便可立即執行,這時的程序狀態稱為就緒狀態。
(2)執行/執行(Running)狀態當程序已獲得處理機,其程式正在處理機上執行,此時的程序狀態稱為執行狀態。
(3)阻塞(Blocked)狀態正在執行的程序,由於等待某個事件發生而無法執行時,便放棄處理機而處於阻塞狀態。引起程序阻塞的事件可有多種,例如,等待I/O完成、申請緩衝區不能滿足、等待信件(訊號)等。
    事件請求:input、sleep、檔案輸入輸出、recv、accept等
    事件發生:sleep、input等完成了
    時間片到了之後有回到就緒狀態,這三個狀態不斷的在轉換

2.同步非同步

1.所謂同步就是一個任務的完成需要依賴另外一個任務時,只有等待被依賴的任務完成後,依賴的任務才能算完成,這是一種可靠的任務序列
2.所謂非同步是不需要等待被依賴的任務完成,只是通知被依賴的任務要完成什麼工作,依賴的任務也立即執行,只要自己完成了整個任務就算完成了。
至於被依賴的任務最終是否真正完成,依賴它的任務無法確定,所以它是不可靠的任務序列。

3.阻塞與非阻塞
阻塞和非阻塞這兩個概念與程式(執行緒)等待訊息通知(無所謂同步或者非同步)時的狀態有關。
也就是說阻塞與非阻塞主要是程式(執行緒)等待訊息通知時的狀態角度來說的


4.同步/非同步與阻塞和非阻塞

同步阻塞形式:效率最低。拿上面的例子來說,就是你專心排隊,什麼別的事都不做。

非同步阻塞形式:如果在排隊取餐的人採用的是非同步的方式去等待訊息被觸發(通知),也就是領了一張小紙條,假如在這段時間裡他不能做其它的事情,就在那坐著等著,不能玩遊戲等,那麼很顯然,這個人被阻塞在了這個等待的操作上面;
非同步操作是可以被阻塞住的,只不過它不是在處理訊息時阻塞,而是在等待訊息通知時被阻塞。

同步非阻塞形式:實際上是效率低下的。想象一下你一邊打著電話一邊還需要擡頭看到底隊伍排到你了沒有,如果把打電話和觀察排隊的位置看成是程式的兩個操作的話,
這個程式需要在這兩種不同的行為之間來回的切換,效率可想而知是低下的。

非同步非阻塞形式:效率更高,因為打電話是你(等待者)的事情,而通知你則是櫃檯(訊息觸發機制)的事情,程式沒有在兩種不同的操作中來回切換。

tips:很多人會把同步和阻塞混淆,是因為很多時候同步操作會以阻塞的形式表現出來,同樣的,很多人也會把非同步和非阻塞混淆,因為非同步操作一般都不會在真正的IO操作處被阻塞。


6.multiprocess模組
1.process模組介紹
process模組是一個建立程序的模組,藉助這個模組,就可以完成程序的建立。

主程序建立的子程序是非同步執行的,那麼我們就驗證一下,並且看一下子程序和主程序(也就是父程序)的ID號(講一下pid和ppid,使用pycharm舉例),來看看是否是父子關係。
from multiprocessing import Process
import time,os
def func1():
time.sleep(2)
print("我是func1")
print("主程序>>",os.getpid())
def func2():
time.sleep(2)
print("我是func2")
print("子程序>>",os.getpid())
print("子程序的父程序>>", os.getppid())

if __name__ == "__main__":
p = Process(target=func2)
p.start()
func1()
print("主程序的父程序>>",os.getppid())

Process類中引數的介紹:
引數介紹:
1 group引數未使用,值始終為None
2 target表示呼叫物件,即子程序要執行的任務
3 args表示呼叫物件的位置引數元組,args=(1,2,'egon',)
4 kwargs表示呼叫物件的字典,kwargs={'name':'egon','age':18}
5 name為子程序的名稱

給要執行的函式傳引數:
from multiprocessing import Process
import time
def func(m,n):
print(m)
time.sleep(1)
print(n)
if __name__ == '__main__':
p1 = Process(target=func,args=(10,20))
p1.start()

Process類中各方法的介紹:
1 p.start():啟動程序,並呼叫該子程序中的p.run()
2 p.run():程序啟動時執行的方法,正是它去呼叫target指定的函式,我們自定義類的類中一定要實現該方法
3 p.terminate():強制終止程序p,不會進行任何清理操作,如果p建立了子程序,該子程序就成了殭屍程序,使用該方法需要特別小心這種情況。如果p還儲存了一個鎖那麼也將不會被釋放,進而導致死鎖
4 p.is_alive():如果p仍然執行,返回True
5 p.join([timeout]):主執行緒等待p終止(強調:是主執行緒處於等的狀態,而p是處於執行的狀態)。
timeout是可選的超時時間,需要強調的是,p.join只能join住start開啟的程序,而不能join住run開啟的程序

 Process類中自帶封裝的各屬性的介紹
1 p.daemon:預設值為False,如果設為True,代表p為後臺執行的守護程序,當p的父程序終止時,p也隨之終止,並且設定為True後,p不能建立自己的新程序,必須在p.start()之前設定
2 p.name:程序的名稱
3 p.pid:程序的pid
4 p.exitcode:程序在執行時為None、如果為–N,表示被訊號N結束(瞭解即可)
5 p.authkey:程序的身份驗證鍵,預設是由os.urandom()隨機生成的32字元的字串。這個鍵的用途是為涉及網路連線的底層程序間通訊提供安全性,這類連線只有在具有相同的身份驗證鍵時才能成功(瞭解即可)

2.Process類的使用

程序的建立第二種方法(繼承)
from multiprocessing import Process
import time
class myprocess(Process):
def __init__(self,n):
super().__init__()
self.n = n
def run(self): #想要定義功能,必須採用run的名字,因為target會呼叫名稱為run的函式
time.sleep(2)
print("我是run")
if __name__ == '__main__':
p1 = myprocess(10)
p1.start()


程序之間的資料是隔離的:
驗證例項:
from multiprocessing import Process
import time,os
num = 100
def func():
global num
num -= 1
print("子程式pid:%s,子程式的父程式%s,num=%s"%(os.getpid(),os.getppid(),num))
lis = []
if __name__ == '__main__':
for i in range(10):
p = Process(target=func)
p.start()
lis.append(p)
for el in lis:
el.join()
print("主程式pid:%s,num=%s"%(os.getpid(),num))


程序物件的其他方法一:terminate,is_alive

from multiprocessing import Process
import time
import random

class Piao(Process):
def __init__(self,name):

super().__init__()
self.name = name
#
def run(self):
print('%s is 玩泥巴' %self.name)
# s = input('???') #別忘了再pycharm下子程序中不能input輸入,會報錯EOFError: EOF when reading a line,因為子程序中沒有像我們主程序這樣的在pycharm下的控制檯可以輸入東西的地方
time.sleep(2)
print('%s is 玩泥巴結束' %self.name)

if __name__ == '__main__':
p1 = Piao("尼古拉斯")
p1.start()
time.sleep()
p1.terminate()#關閉程序,不會立即關閉,有個等著作業系統去關閉這個程序的時間,所以is_alive立刻檢視的結果可能還是存活,但是稍微等一會,就被關掉了
print(p1.is_alive()) #結果為True
print('等會。。。。')
time.sleep(1)
print(p1.is_alive()) #結果為False

7.殭屍程序與孤兒程序(簡單瞭解 一下就可以啦)
  殭屍程序:一個程序使用fork建立子程序,如果子程序退出,而父程序並沒有呼叫wait或waitpid獲取子程序的狀態資訊,那麼子程序的程序描述符仍然儲存在系統中。這種程序稱之為僵死程序。詳解如下
我們知道在unix/linux中,正常情況下子程序是通過父程序建立的,子程序在建立新的程序。子程序的結束和父程序的執行是一個非同步過程,即父程序永遠無法預測子程序到底什麼時候結束,如果子程序一結束就立刻回收其全部資源,那麼在父程序內將無法獲取子程序的狀態資訊。

孤兒程序:一個父程序退出,而它的一個或多個子程序還在執行,那麼那些子程序將成為孤兒程序。孤兒程序將被init程序(程序號為1)所收養,並由init程序對它們完成狀態收集工作。
孤兒程序是沒有父程序的程序,孤兒程序這個重任就落到了init程序身上,init程序就好像是一個民政局,專門負責處理孤兒程序的善後工作