1. 程式人生 > >關於程序,執行緒,多程序和多執行緒的網路程式設計

關於程序,執行緒,多程序和多執行緒的網路程式設計

程序執行緒網路

多工程式設計 : 可以有效的利用計算機資源,同時執行多個任務

程序 : 程序就是程式在計算機中一次執行的過程

程序和程式的區別:

程式是一個靜態檔案的描述,不佔計算機的系統資源
程序是一個動態的過程,佔有cpu記憶體等資源,有一定的生命週期

* 同一個程式的不同執行過程即為不同的程序

問題1  什麼決定了程序的建立

使用者通過應用層程式進行程序的建立申請 ----》

呼叫作業系統介面進行程序建立 -----》

告知系統核心建立新的程序提供給應用層使用


問題2  程序如何佔有CPU

1. 同一個核心同一時刻只能執行一個程序 
2. 多個程序對核心資源進行搶佔,由作業系統核心進行分配
3. 哪個程序佔有計算機核心我們稱為該程序佔有CPU的時間片

問題3  程序在執行過程中的形態和附帶內容

PCB(程序控制塊) : 在linux和unix作業系統中,程序建立後會在記憶體中開闢一塊空間存放程序的相關資訊,這個空間稱之為PCB

PID:在作業系統中程序的唯一標識,是一個大於0的正整數,由系統自動分配

ps -aux 檢視程序資訊

虛擬記憶體 : 每個程序佔有4G記憶體地址空間 ,這裡的記憶體指的是虛擬記憶體

程序狀態

三態 : 

就緒態 : 程序具備執行條件,等待系統分派處理器以便執行
執行態 : 程序佔有cpu處於執行狀態 
等待態 : 又稱為阻塞態或者睡眠態,指程序不具備執行條件,正在等待某些條件的達成


五態:

新建態:建立一個程序的過程,直接表現為執行某個程式或者在程式中建立新的程序

終止態:程序執行結束,完成回收的過程

D  等待態 (不可中斷)
S  等待態  (可中斷)
T  等待態   (暫停)

R  執行態
Z  殭屍態

+  前臺程序
N  低優先順序的程序
<  高優先順序的程序
l  有程序連結
s  會話組

程序的優先順序

優先順序往往決定了一個程序的執行許可權和佔有系統資源的優先程度

top : 動態檢視系統程序執行情況

<  >  進行翻頁查詢

linux 系統中優先順序範圍 -20 --- 19  -20優先順序最高
使用者建立程序預設優先順序為0

nice : 以指定的優先順序執行某個程序

e.g.   nice -9  ./while.py   以9 的優先順序執行程式

sudo nice --9  ./while.py  以-9的優先順序執行程式

renice  n  PID : 修改一個正在執行的程序的優先順序

e.g.  renice  8   4277  將4277號程序優先順序修改為8

父子程序:在系統中除了初始化程序之外每個程序都是由父程序建立的,每個程序有一個唯一的父程序,可能有多個子程序。

pstree

總結 :

1. 什麼是程序
2. 程序和程式的區別
3. 程序的幾種狀態及相互建的轉換
4. 什麼是  PCB  PID  cpu時間片

需求: 兩件不相關事情希望同時來做

方案1 : 寫兩個程序,分別承擔不同的事情,各自執行
分析 :1. 兩個程式比較麻煩
       2. 無法確定兩個程式應該在什麼時間開始執行

方案2 : 寫一個程式,在程式中指定位置呼叫介面來建立新的程序

通過 os.fork() 函式實現

fork()
功能 : 建立一個新的程序
引數 : 無
返回值 :  < 0 表示程序建立失敗
         == 0 在子程序中fork的返回值為0
                  > 0 在父程序中fork的返回值大於0

* fork是os模組函式,只能在linux和unix下使用

測試1: 父程序中fork之前的內容,子程序同樣也會複製
        但是父子程序空間內容的修改不會相互影響

測試2: 父子程序在執行上互不影響,理論上不一定誰先執行

測試3: 子程序雖然複製父程序的空間,但是也有自己獨特的特性,比如 自己的PID,程序控制塊,程序棧等。父程序中fork的返回值即    為建立的子程序的PID號

程序相關函式

os.getpid()
功能 : 獲取當前程序的PID號

os.getppid()
功能 : 獲取當前程序父程序的PID號

結束一個程序
os._exit(status)
功能 : 結束一個程序
引數 : 一個數字表示程序的退出狀態 通常0表示正常退出程序其他數字表示非正常退出

sys.exit([status])
功能 : 結束一個程序,如果處理了丟擲的異常則不結束程序

引數 : 一個數字表示程序的退出狀態 同上
        還可以是一個字串,則在程序退出示會列印這個字串


殭屍程序:子程序先於父程序退出,父程序沒有對子程序的退出做相應的處理,此時子程序就會變為殭屍程序。

影響 :  程序退出後,仍有部分資訊殘留在記憶體中佔用空間,大量的殭屍程序會影響系統執行。所以應該進來避免殭屍程序的產生。


孤兒程序: 父程序先於子程序退出,此時子程序就會變為孤兒程序

影響 : 當一個程序變為孤兒程序,系統會自動的使用一個程序稱為孤兒程序的父程序。當孤兒程序退出時,該系統程序會自動回收

處理殭屍程序的方法:

1、讓父程序先退出   (不好控制)
2、父程序處理子程序的退出  (阻塞父程序的執行)

os.wait()
功能:等待子程序退出進行處理
引數:無
返回值 : 返回一個包含兩個元素的元組,第一個是退出的子程序的PID號,第二個是子程序的退出狀態

* wait是一個阻塞函式 即 程序處於等待態,等待某種條件的達成才會繼續執行

os.waitpid(pid,option) 
功能 : 同wait 處理子程序退出使其不會變成殭屍
引數 : pid   -1  表示等待任意子程序退出
              >0  表示等待指定程序號的子程序退出
                option   0  表示阻塞等待
                        WNOHANG : 表示非阻塞狀態
返回值 : 同wait

wait() ====>  waitpid(-1,0)

總結

1.函式的使用  fork   getpid  getppid  _exit exit  wait

waitpid

2. 理解什麼是殭屍程序什麼是孤兒程序即兩者產生的過程

3. 知道殭屍程序的危害和兩種處理方法

4. 理解程序的建立流程 

建立二級子程序處理

在父程序中使用訊號處理的方法忽略子程序發來的訊號
signal(SIGCHLD,SIG_IGN)

更方便高效的程序建立方法

multiprocessing模組 (標準庫模組) 

建立的程序的步驟

1. 將要完成的事件封裝成一個個函式
2. 使用multiprocessing提供的介面函式建立程序
3. 使新的程序和指定的函式相關聯去完成函式中的工作
4. 對程序進行回收處理

* 函式當付給Process 的target變數後函式內容就是對應程序的程序內容,此時函式才有特殊性
* 多個子程序和父程序之間的執行相互不影響

建立子程序

Process() 類
引數: target  指定要繫結的函式
       name  給建立的程序起一個名字
             args  需要一個元組,給target指定的函式按位置傳參
             kwargs 需要給一個字典,給target指定的函式按鍵值        傳參

Process() 類 ----- 》 p 程序物件

屬性方法
print("程序名稱:",p.name)
print("程序PID:",p.pid)
print('程序狀態:',p.is_alive())
         

啟動子程序

start()
* start() 時才真正的建立子程序,而不是Process時建立

回收子程序

join([timeout])
timeout : 設定最長阻塞時間,如果超過這個時間還沒有子程序退出則不再繼續等待

* 核心會幫助應用層記錄子程序的退出情況,當使用join函式時核心會及時返回程序狀態給應用層進行處理

p.daemon

預設值為False 表示主程序執行結束後 不會影響子程序的執行,知道子程序執行完,程序才會結束

如果設定為True 則主程序執行完畢則所有子程序也不再執行一起退出

* 該屬性的設定必須要在start()前
* 該屬性的設定並不是 將程序設定為 linux/unix中的守護程序

守護程序: 生命週期長, 與前端控制檯無關,後臺執行,一般用作系統程序或者自動化執行程序

多程序程式設計

優點: 可以並行的執行多個任務,提高執行效率
            空間獨立,資料安全
            建立方便 

缺點:程序的建立和銷燬過程需要消耗較多的計算機資源

在需要頻繁的建立和刪除較多程序的情況下,資源消耗過多,不適宜使用多程序完成任務

程序池技術

1. 建立程序池 ,在池內放入合適數量的程序
2. 將事件加入程序池的等待佇列
3. 使用程序池內的程序不斷的執行等待事件
4. 所有事件處理結束後關閉回收程序池

Pool 
功能:建立程序池
引數: processes :程序池中程序的數量

apply_async() 
功能: 以非同步的方式將要執行的事件放入程序池
引數: func : 要執行的函式
       args : 給函式按位置傳參
         kwds : 給函式按照鍵值傳參
返回值 : 返回事件執行後的返回值物件,可以通過呼叫get() 函式獲取事件函式return的內容

apply()
功能 : 按照順序新增要執行的時間,執行一個再新增一個


close() 
功能 : 關閉程序池,使其不能再加入新的事件

join()
功能:阻塞等待程序池將時間都執行結束後回收程序池

map()
功能 : 類似與內建函式map 將第二個引數的迭代物件中的資料逐個帶入第一個函式作為引數。只不過兼顧了apply_async功能

pool.map(fun,test)   ====> 

for i in test:
    pool.apply_async(fun,(i,))

建立自己的程序類

1. 繼承Process類以獲取原有的屬性
2. 實現自己需要的功能部分
3. 使用自己的類建立程序即可

程序間通訊

管道   訊息佇列   共享記憶體    訊號   套接字

管道

在記憶體中開闢一個管道空間,對多個程序可見。在通訊形式上形成一種約束。

linux  檔案型別  
b c    d        -         l     s      p
      目錄   普通檔案   連結 套接字 管道

multiprocessing  ---》 Pipe 函式

Pipe(duplex)
功能 : 建立一個管道 
引數 : duplex 預設為 True  表示管道為雙向管道
    如果設定為False 則表示管道為單向管道

返回值 : 返回兩個管道流物件,分別表示管道的兩端

                如果引數為True(預設) 兩個物件均可傳送接受
                如果為False時 則第一個物件只能接受,第二個物件只能傳送

* 向管道傳送資料使用send()函式,從管道接受資料使用recv()函式
* recv()函式為阻塞函式,當管道中資料為空的時候會阻塞
* 一次recv() 只能接受一次send()的內容
* send()可以傳送字串數字列表等多種型別資料

訊息佇列

multiprocessing  --- 》 Queue  

在記憶體中開闢一個佇列模型,用來存放訊息。任何擁有佇列物件的程序都可以進行訊息的存放和取出

Queue(maxsize = 0)
功能 : 建立一個訊息佇列物件
引數 : maxsize  預設為0  表示訊息佇列可以存放的訊息有                  系統自動分配的空間而定
             > 0 正整數  表示佇列中最多存放多少條訊息
返回值 : 訊息佇列物件

q.put()
向訊息佇列中存放一條訊息,當訊息佇列滿的時候,會阻塞
存放的訊息型別可以使數字列表,字串等

q.full()
判斷佇列是否為滿,如果滿則返回True 否則返回False

q.qsize()
檢視當前佇列中訊息數量

q.get()
獲取訊息,每次獲取一條,當訊息佇列為空是,則阻塞

q.empty()
訊息佇列為空則返回True 不為空返回False

* put  get 中block引數和timeout引數
block 預設為True 表示兩個函式都是阻塞函式
  如果設定為False則表示不阻塞

timeout  當block設定為True的時候表示超時等待時間

共享記憶體

在記憶體中開闢一段記憶體空間儲存資料,每次儲存的內容會覆蓋上次的內容。由於沒有對記憶體進行格式化的修飾所以存取速度塊效率高

from multiprocessing import Value,Array

obj = Value(ctype,obj)
功能 : 開闢共享記憶體
引數 : ctype  要轉變的c的型別
            obj    要寫入共享記憶體的初始值

obj.value 屬性為獲取共享記憶體中的值 

obj = Array(ctype,obj)
功能 : 開闢一個共享記憶體空間
引數 : 要轉換的c的型別
        obj : 放入共享記憶體中的資料,是一個列表,要求列        表中的資料為相同型別資料
                            
                             如果obj傳入一個正數,則表示在共享記憶體中開闢一個 多大的空間,空間中可以存放的數值型別

訊號 :

kill -l  檢視系統訊號
kill  -signame  PID  給程序號PID的程序傳送signame訊號

訊號 : 訊號名稱     含義     預設處理方法

名稱 : 系統定義 
含義 : 系統定義
處理方式 : 採用預設方式處理 (系統定義 終止 暫停 忽略
            忽略訊號(當訊號沒發生過)
                        採用自定義的方式處理

如何傳送訊號:

os.kill(pid,sig)
功能 : 向一個程序傳送一個訊號
引數 : pid :要傳送程序的PID號
        sig :要傳送的訊號

signal.alarm(sec) 
功能:給自己傳送一個時鐘訊號 (SIGALRM)
引數: sec : 秒數 表示在相應的秒數後傳送時鐘訊號

* 訊號是一種非同步的程序間通訊方式
* alarm 函式在一個程序中如果使用多次,則後面的時鐘時間會覆蓋前面的時間

訊號的處理:

signal.pause() 
阻塞等待一個訊號的發生

signal.signal(signum,handler)
功能 : 處理訊號
引數 : signum : 表示可以處理的訊號
        handler : 訊號的處理方法

                           預設處理方式 : SIG_DFL
忽略訊號     : SIG_IGN
自定義的方式 : function

* signal函式也是一個非同步處理訊號函式
* SIGSTOP 和 SIGKILL不能被signal函式處理

殭屍程序的訊號處理方案 父程序中
signal(SIGCHLD,SIG_IGN)

同步和互斥

臨界資源 : 對多個程序或者執行緒都可見的資源,容易產生爭奪,我們將這類資源稱為臨界資源

臨界區 : 對臨界資源進行操作的程式碼區域稱之為臨界區

解決資源爭奪: 同步   或者  互斥

同步 : 同步是一種合作關係,為完成某種任務而建立的多個程序或者執行緒之間的協調呼叫,次序等待,傳遞訊息告知資源佔用情況

互斥 : 互斥是一種制約關係,當一個程序或者執行緒進入到臨界區後會進行枷鎖操作,此時其他程序(執行緒)無法進如臨界區,只有當該
                      程序(執行緒)使用後進行解鎖,其他人才可以使用。這種技術往往是通過阻塞完成

程序間通訊

管道   Pipe  recv  send 

佇列   Queue   qsize   full     empty   put  get

共享記憶體  Value   Array

訊號  kill   pause    alarm    signal


同步和互斥

Event() ----> 事件物件e 

e.wait()  : 產生一種阻塞 ,知道e被set之後才結束阻塞
e.set()   : 將e  set操作 wait不再阻塞
e.is_set() : 判斷e是否是被設定的狀態
e.clear() : 將e  變成 沒有被設定的狀態

程序間同步互斥方法

from multiprocessing import Lock

建立 程序鎖物件
lock = Lock()

lock.acquire()  給臨界區上鎖
lock.release()  給臨界區解鎖

* 具體實現上 acquire() 為一個條件阻塞函式  當有任意一個程序先進行了acquire操作後,其他程序再企圖進行acquire操作時就會阻塞,
        直到lock物件被 release 後其他程序才可進行下次acquire操作


with lock:  也可以實現加鎖 解鎖

執行緒

* 執行緒也可以使用計算機的多核資源,也是多工程式設計方式之一
* 執行緒又稱為輕量級的程序,在併發上和程序相同但是在建立時消耗資源少

一個程序中可以包含多個執行緒,這多個執行緒共享程序的資源
多個執行緒因為共享程序的資源所以在通訊上往往採用全域性變數的方法
執行緒也有自己特有的資源,比如 TID 指令集等

多程序和多執行緒的區別和聯絡

1. 多程序和多執行緒都是多工程式設計方式,都可以使用計算機多核
2. 程序的建立要比執行緒消耗更多的資源
3. 程序空間獨立資料更安全,有專門的程序間通訊方式進行互動
4. 一個程序包含多個執行緒,所以執行緒共享程序資源,沒有轉門的通訊方法,依賴全域性量進行通訊。往往需要使用同步互斥機制,邏輯需 
   要考慮更多
5. 程序執行緒都有自己特有的資源。多個關聯任務的時候使用多執行緒資源消耗更少,如果是多個無關任務也不適於全都使用執行緒

建立執行緒

import  threading 
建立執行緒函式 

threading.Tread()
功能 : 建立執行緒 
引數 : target  執行緒函式 
                args   以元組方式給執行緒函式傳參
                kwargs  以字典方式給執行緒函式傳參
                name   執行緒名稱   (預設Thread-1)
返回值 : 返回執行緒物件

執行緒屬性和方法

t.start()  啟動一個執行緒
t.is_alive()  檢視一個執行緒的狀態
t.name    檢視執行緒的名稱
t.join([sec])  阻塞等待回收執行緒

daemon 屬性

設定 該屬性預設為False 主執行緒執行完畢不會影響其他執行緒的執行
如果設定為True 則主執行緒執行完畢其他執行緒也終止執行
t.setDaemon(True)
或
t.daemon = True

獲取daemon屬性值

t.isDaemon()

執行緒間的通訊

全域性變數進行通訊

執行緒間的同步和互斥

執行緒 event

建立事件物件
e = threading.Event()

e.wait([timeout]) 如果e被設定則不會阻塞,未被設定則阻塞
                                        timeout為阻塞的超時時間

e.set()  將e變為設定的狀態

e.clear()  將e變為未設定的狀態

執行緒鎖

lock = threading.Lock()  建立鎖物件

lock.acquire()   上鎖

lock.release()   解鎖

建立自己的執行緒類

1. 自定義類 繼承於 原有執行緒類 Thread

2. 複寫原有的run方法 

3. 建立執行緒物件呼叫start 的時候會自動執行run


threadpool 執行緒池第三方模組

sudo pip3 install threadpool

GIL (全域性直譯器鎖)

python -- 》 支援多執行緒  ----》 同步和互斥 ---》加鎖 ---》超級鎖 ----》 直譯器在同一時刻只能解釋一個執行緒

大量python庫為了省事依賴於這種機制 ---》 python多執行緒效率低

GIL 即為從python 直譯器由於上鎖帶了的同一時刻只能解釋一個執行緒的問題

解決方案 : 
* 不使用執行緒 轉而使用程序
* 不實用c作為直譯器  java  c#都可以做python直譯器

IO 密集型 程式中進行了大量IO操作,只有少量的CPU操作

在記憶體中進行了資料的交換的操作都可以認為是IO操作
特點 : 速度較慢,使用cpu不高                       

cpu密集型(計算密集型):大量的程式都在進行運算操作

特點 : cup佔有率高

效率測試: 

Line cpu 1.224205732345581
Line IO 4.142379522323608

Thread cpu 0.7009162902832031
Thread IO 3.458016872406006

Process cpu 0.6419346332550049
Process IO 1.8482108116149902

* 多執行緒的工作效率和單執行緒幾乎相近,而多程序要比前兩者有明顯的效率提升

設計模式

設計模式代表了一種最佳實踐,是被開發人員長期開發總結,用來解決某一類問題的思路方法。這些方法保證了程式碼的效率也易於理  
  解。

單例模式   工廠模式    生產者模式。。。。


生產者消費者模式

高內聚 : 在同一模組內,實現單一功能,儘量不使功能混雜

低耦合 : 不同的模組之間儘量相互獨立,減少模組間的影響

總結

1.程序和執行緒的區別
2.會建立使用執行緒 threading
3.掌握基本的執行緒間同步互斥程式設計方法
4.知道什麼是GIL
5.瞭解設計模式的概念