1. 程式人生 > 實用技巧 >python-程序

python-程序

什麼是程序

程序(Process)是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。在早期面向程序設計的計算機結構中,程序是程式的基本執行實體;在當代面向執行緒設計的計算機結構中,程序是執行緒的容器,執行緒是程式的基本執行實體。程式是指令、資料及其組織形式的描述,程序是程式的實體。

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

注意:同一個程式執行兩次,就會在作業系統中出現兩個程序,所以我們可以同時執行一個軟體,分別做不同的事情也不會混亂。

程序排程

要想多個程序交替執行,作業系統必須對這些程序進行排程,這個排程也不是隨機進行的,而是需要遵循一定的原則,由此就有了程序的排程演算法。

先來先服務排程演算法 短作業優先排程演算法 時間片輪轉法 多級反饋佇列

程序的並行與併發

並行:並行是指兩者同時執行,比如有兩條車道,在某一個時間點,兩條車道上都有車在跑;(資源夠用,比如三個執行緒,四核的CPU )

併發:併發是指資源有限的情況下,兩者交替輪流使用資源,比如只有一條車道(單核CPU資源),那麼就是A車先走,在某個時刻A車退出把道路讓給B走,B走完繼續給A ,交替使用,目的是提高效率。

區別:

並行是從微觀上,也就是在一個精確的時間片刻,有不同的程式在執行,這就要求必須有多個處理器。
併發是從巨集觀上,在一個時間段上可以看出是同時執行的,比如一個伺服器同時處理多個session。

注意:早期單核CPU時候,對於程序也是微觀上序列(站在cpu角度看),巨集觀上並行(站在人的角度看就是同時有很多程式在執行)。

同步非同步阻塞非阻塞

狀態介紹

  在瞭解其他概念之前,我們首先要了解程序的幾個狀態。在程式執行的過程中,由於被作業系統的排程演算法控制,程式會進入幾個狀態:就緒,執行和阻塞。

  (1)就緒(Ready)狀態

  當程序已分配到除CPU以外的所有必要的資源,只要獲得處理機便可立即執行,這時的程序狀態稱為就緒狀態。

  (2)執行/執行(Running)狀態當程序已獲得處理機,其程式正在處理機上執行,此時的程序狀態稱為執行狀態。

  (3)阻塞(Blocked)狀態正在執行的程序,由於等待某個事件發生而無法執行時,便放棄處理機而處於阻塞狀態。引起程序阻塞的事件可有多種,例如,等待I/O完成(input)、申請緩衝區不能滿足、等待信件(訊號)等。

同步和非同步

所謂同步就是一個任務的完成需要依賴另外一個任務時,只有等待被依賴的任務完成後,依賴的任務才能算完成,這是一種可靠的任務序列。要麼成功都成功,失敗都失敗,兩個任務的狀態可以保持一致。

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

例子

阻塞與非阻塞

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

例子

同步/非同步與阻塞/非阻塞

  1. 同步阻塞形式

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

  1. 非同步阻塞形式

  如果在銀行等待辦理業務的人採用的是非同步的方式去等待訊息被觸發(通知),也就是領了一張小紙條,假如在這段時間裡他不能離開銀行做其它的事情,那麼很顯然,這個人被阻塞在了這個等待的操作上面;

  非同步操作是可以被阻塞住的,只不過它不是在處理訊息時阻塞,而是在等待訊息通知時被阻塞。

  1. 同步非阻塞形式

  實際上是效率低下的。

  想象一下你一邊打著電話一邊還需要抬頭看到底隊伍排到你了沒有(兩個操作不能同時執行,因為是同步),如果把打電話和觀察排隊的位置看成是程式的兩個操作的話,這個程式需要在這兩種不同的行為之間來回的切換,效率可想而知是低下的。

  1. 非同步非阻塞形式

  效率更高,

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

  比如說,這個人突然發覺自己煙癮犯了,需要出去抽根菸,於是他告訴大堂經理說,排到我這個號碼的時候麻煩到外面通知我一下,那麼他就沒有被阻塞在這個等待的操作上面,自然這個就是非同步+非阻塞的方式了。

  

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

程序的建立與結束

程序的建立

  但凡是硬體,都需要有作業系統去管理,只要有作業系統,就有程序的概念,就需要有建立程序的方式,一些作業系統只為一個應用程式設計,比如微波爐中的控制器,一旦啟動微波爐,所有的程序都已經存在。

  而對於通用系統(跑很多應用程式),需要有系統執行過程中建立或撤銷程序的能力,主要分為4中形式建立新的程序:

  1. 系統初始化(檢視程序linux中用ps命令,windows中用工作管理員,前臺程序負責與使用者互動,後臺執行的程序與使用者無關,執行在後臺並且只在需要時才喚醒的程序,稱為守護程序,如電子郵件、web頁面、新聞、列印)

  2. 一個程序在執行過程中開啟了子程序(如nginx開啟多程序,os.fork,subprocess.Popen等)

  3. 使用者的互動式請求,而建立一個新程序(如使用者雙擊暴風影音)

  4. 一個批處理作業的初始化(只在大型機的批處理系統中應用)

  無論哪一種,新程序的建立都是由一個已經存在的程序執行了一個用於建立程序的系統呼叫而建立的。  

建立程序

程序的結束

  1. 正常退出(自願,如使用者點選互動式頁面的叉號,或程式執行完畢呼叫發起系統呼叫正常退出,在linux中用exit,在windows中用ExitProcess)

  2. 出錯退出(自願,python a.py中a.py不存在)

  3. 嚴重錯誤(非自願,執行非法指令,如引用不存在的記憶體,1/0等,可以捕捉異常,try...except...)

  4. 被其他程序殺死(非自願,如kill -9)

在python程式中的程序操作

  之前我們已經瞭解了很多程序相關的理論知識,瞭解程序是什麼應該不再困難了,剛剛我們已經瞭解了,執行中的程式就是一個程序。所有的程序都是通過它的父程序來建立的。因此,執行起來的python程式也是一個程序,那麼我們也可以在程式中再建立程序。多個程序可以實現併發效果,也就是說,當我們的程式中存在多個程序的時候,在某些時候,就會讓程式的執行速度變快。以我們之前所學的知識,並不能實現建立程序這個功能,所以我們就需要藉助python中強大的模組。

multiprocessing模組

仔細說來,multiprocessing不是一個模組而是python中一個操作、管理程序的包。 之所以叫multi是取自multiple的多功能的意思,在這個包中幾乎包含了和程序有關的所有子模組。由於提供的子模組非常多,為了方便大家歸類記憶,我將這部分大致分為四個部分:建立程序部分,程序同步部分,程序池部分,程序之間資料共享。

multiprocessing.process模組

process模組介紹

process模組是一個建立程序的模組,藉助這個模組,就可以完成程序的建立。

View Code 方法介紹 屬性介紹 在windows中使用process模組的注意事項

使用process模組建立程序

在一個python程序中開啟子程序,start方法和併發效果。

python 開啟第一個子程序 join 方法 檢視子程序和父程序的id號

進階,多個程序同時執行(注意:子程序的執行順序並不受開啟子程序的順序影響)

多個程序同時執行 join在搞事情(1) join在搞事情(2)

上邊是直接開啟多程序,接下來介紹一個高大上的方法 -- 繼承Process類的方式

繼承那些事(Process)

多程序之間關於資料隔離的那些年那些事兒

多程序之間資料隔離

守護程序

會隨著父程序的結束而結束。

父程序建立守護程序

  其一:守護程序會在父程序程式碼執行結束後就終止

  其二:守護程序內無法再開啟子程序,否則丟擲異常:AssertionError: daemonic processes are not allowed to have children

注意:程序之間是互相獨立的,父程序程式碼執行結束,守護程序隨即終止

守護程序的啟動 守護程序會隨著父程序結束而結束

socket tcp協議併發實現聊天

socket-tcp協議併發實現_server socket-tcp協議併發實現_client

多程序中其他方法

程序的terminate和is_alive方法 程序的name和pid屬性

程序同步(multiprocessing.Lock、multiprocessing.Semaphore、multiprocessing.Event)

在計算機中,有一些硬體和軟體,例如處理器、印表機等,都屬於競爭類資源,當有需求時,很多程序都要爭搶這些資源,而對於這類資源,就屬於臨界資源。當多程序共同處理某一個數據時,這個資料也就屬於一個臨界資源。作業系統對計算機內各種資源都使其在競爭中有序化,但是對於資料來說,尤其是使用者動態產生的資料,當處理時就變成了臨界資源,所以我們作為程式猿來說,需要對臨界資源加以保護,否則就會出現資料混亂現象。這是在提高程式效率的優勢下,帶來的一個隱患。小夥伴們,加油吧!

鎖 —— multiprocessing.Lock

通過剛剛的學習,我們千方百計實現了程式的非同步,讓多個任務可以同時在幾個程序中併發處理,他們之間的執行沒有順序(或者說由作業系統排程決定他們的順序),一旦開啟也不受我們控制。儘管併發程式設計讓我們能更加充分的利用IO資源,但是也給我們帶來了新的問題。

  當多個程序使用同一份資料資源的時候,就會引發資料安全或順序混亂問題。

多程序關於搶佔輸出資源的事情 使用鎖維護輸出資源

上面這種情況,使用了加鎖的形式確保了程式的順序執行,但是執行又變成了序列,降低了效率,但是不得不說,它確保了資料的安全性。

下面舉例來說鎖的重要性:模擬12306搶票問題。模擬銀行賬戶的存取款問題。

多個人同時搶票

很明顯,上述例子中,因為多程序同時對一個臨界資源(a.txt檔案)進行了讀寫操作,使檔案內資料混亂,也造成了餘票為1張,但是很多人都搶到票的假象。那就加鎖來解決它吧

加鎖解決買票問題

關於銀行存取款的問題。同一個賬戶,某個人一直存,某個人在同一時間一直取,如果不對資料進行保護起來,就會造成的一種資料混亂問題。

錢多錢少怪誰? 這樣才對!!!

訊號量 —— multiprocessing.Semaphore(瞭解)

訊號量 Semaphore 訊號量機制舉個栗子

事件 —— multiprocessing.Event(瞭解)

事件 Event

時間機制舉個栗子

程序間通訊——佇列和管道(multiprocess.Queue、multiprocess.Pipe)

程序間通訊--IPC(Inter-Process Communication)

IPC的方法:此處介紹佇列和管道

佇列

概念:建立共享的程序佇列,Queue是多程序安全的佇列,可以使用Queue實現多程序之間的資料傳遞。

Queue([maxsize]) 
建立共享的程序佇列。
引數 :maxsize是佇列中允許存在的最大元素個數。如果省略此引數,則無大小限制。
底層佇列使用管道和鎖定實現。
Queue中的方法介紹 需要你瞭解的幾個方法

程式碼示例

瞭解佇列的用法

上面這個例子還沒有加入程序通訊,只是先來看看佇列為我們提供的方法,以及這些方法的使用和現象。

多程序中使用佇列

上面是一個queue的簡單應用,使用佇列q物件呼叫get函式來取得佇列中最先進入的資料。 接下來看一個稍微複雜一些的例子:

批量資料放入佇列並批量獲取

生產者消費者模型

在併發程式設計中使用生產者和消費者模式能夠解決絕大多數併發問題。該模式通過平衡生產程序和消費程序的工作能力來提高程式的整體處理資料的速度。

舉個應用栗子:全棧開發時候,前端接收客戶請求,後端處理請求邏輯。當某時刻客戶請求過於多的時候,後端處理不過來,此時完全可以藉助佇列來輔助,將客戶請求放入佇列中,後端邏輯程式碼處理完一批客戶請求後馬上從佇列中繼續獲取,這樣平衡兩端的效率。

為什麼要使用生產者和消費者模式

程序世界裡,生產者就是生產資料的程序,消費者就是消費資料的程序。在多程序開發當中,如果生產者處理速度很快,而消費者處理速度很慢,那麼生產者就必須等待消費者處理完,才能繼續生產資料。同樣的道理,如果消費者的處理能力大於生產者,那麼消費者就必須等待生產者。為了解決這個問題於是引入了生產者和消費者模式。

什麼是生產者消費者模式

生產者消費者模式是通過一個容器來解決生產者和消費者的強耦合問題。生產者和消費者彼此之間不直接通訊,而通過阻塞佇列來進行通訊,所以生產者生產完資料之後不用等待消費者處理,直接扔給阻塞佇列,消費者不找生產者要資料,而是直接從阻塞佇列裡取,阻塞佇列就相當於一個緩衝區,平衡了生產者和消費者的處理能力。

基於佇列實現生產者消費者模型
基於佇列的生產者消費者模型

上述程式碼是基於佇列實現的生產者消費者模型,生產者一直在生產娃娃,消費者一直在從佇列中獲取娃娃,但是消費者因為不知道生產者要生產多少娃娃,也不知道生產者何時就不生產了,所以消費者需要一個死迴圈一直嘗試去從佇列中獲取娃娃,那麼此時問題就出現了,3個程序,主程序開啟了兩個子程序分別為生產者和消費者,當生產者生產完資料後,生產者結束,消費者一直在嘗試接收資料,那麼問題就出在了消費者的get方法這裡,當get不到資料時候就一直阻塞,那麼主程序就一直等待,此時程式就不會結束了。

解決方法也很簡單,可以嘗試讓生產者在生產完資料後,再往佇列中放一個結束生產的訊號,當消費者接受到訊號後,自動的break出死迴圈即可。

修正版-消費者生產者模型

注意:上述程式碼中,生產者放入的停止生產的標識,放入標識這件事其實交給主程序來做也可以,但是此時就需要主程序獲取到生產者什麼時候結束生產。

主程序傳送結束生產標識

當小夥子們嘗試上述這個程式碼時,發現所有問題都阻擋不了你成功的腳步了,但是!!你有沒有嘗試過多個生產者多個消費者的模型?what fuck? 現在的解決方案是不是就很 low bee了!因為你會發現:(標題)

多個消費者:有幾個消費者就應該放幾個結束生產標識

JoinableQueue([maxsize])

建立可連線的共享佇列程序。它就好像一個Queue物件,但是它自帶光環,允許消費者通知生產者是不是已經消費完所有的資料了。通知程序是使用共享的訊號和條件變數來實現的。

老樣子,先來接收一下JoinableQueue給咱們提供的方法:

JoinableQueue提供的方法

下面舉個栗子,建立一個永遠執行的程序,來無限制的消費生產者的資料,生產者只需要將資料放入佇列並等待被處理即可

JoinableQueue實現生產者消費者模型

管道(瞭解)

管道的介紹 管道的初使用

應該特別注意管道端點的正確管理問題。如果是生產者或消費者中都沒有使用管道的某個端點,就應將它關閉。這也說明了為何在生產者中關閉了管道的輸出端,在消費者中關閉管道的輸入端。如果忘記執行這些步驟,程式可能在消費者中的recv()操作上掛起。管道是由作業系統進行引用計數的,必須在所有程序中關閉管道後才能生成EOFError異常。因此,在生產者中關閉管道不會有任何效果,除非消費者也關閉了相同的管道端點。

自己整個異常玩玩 pipe實現的生產者消費者模型 多個消費者競爭資料帶來了資料不安全的問題

程序直接的資料共享

展望未來,基於訊息傳遞的併發程式設計是大勢所趨

即便是使用執行緒,推薦做法也是將程式設計為大量獨立的執行緒集合,通過訊息佇列交換資料。

這樣極大地減少了對使用鎖定和其他同步手段的需求,還可以擴充套件到分散式系統中。

但程序間應該儘量避免通訊,即便需要通訊,也應該選擇程序安全的工具來避免加鎖帶來的問題。

以後我們會嘗試使用資料庫來解決現在程序之間的資料共享問題。

Manager模組官方說法 Manager模組的簡單用法