本篇開始分析Python中的併發程序,也就是進程、線程、協程序的使用。由於是用自己的語言總結的,因此比較大白話,或者叫通俗易懂。而且很多細節方面沒有具體介紹,因爲在Python進程管理中很少用到,這裏主要分析的就是比較重要的點
基本概念
1、操作系統的本質
幫助我們控制硬件、管理軟件的軟件,對下提供了一個我們控制計算機的接口,對上幫助我們管理應用軟件
2、早期的批處理系統
人機交互過程太多,太過浪費時間和資源,如下圖:
3、解決批處理系統的問題
(1)SPOOLING技術
卡片被拿到機房後能夠很快的將作業從卡片讀入磁盤,於是任何時刻當一個作業結束時,操作系統就能將一個作業從磁帶讀出,裝進空出啦的內存區域運行,這種技術叫做同時的外部設備聯機操作:SPOOLING該技術同時用於輸出。當採用了這種技術後,就不在需要IBM1401機了,也不必將磁帶搬來搬去了(中間倆小人失業了),強化了操作系統的功能。
(2)多道程序設計,用於解決順序執行的問題
在7094機上(程序運行的機器),若當前作業因等待磁帶或等待其他IO操作而暫停,CPU就處於休閒狀態直至IO操作完成,對於CPU密集的科學計算,IO操作少,浪費時間不明顯,對於商業數據處理,IO等待能到達80%~90%,所以必須解決CPU浪費的現象。
4、分時操作系統
分時操作系統=多個聯機終端+多道技術
由於許多程序員懷念第一代獨享的計算機,可以即時調試自己的程序。爲了滿足程序員們很快可以得到響應,出現了分時操作系統。
20個客戶端同時加載到內存,有17在思考,3個在運行,cpu就採用多道的方式處理內存中的這3個程序,由於客戶提交的一般
都是簡短的指令而且很少有耗時長的,索引計算機能夠爲許多用戶提供快速的交互式服務,所有的用戶都以爲自己獨享了計算機資源
5、進程的本質
(1)本質:一個程序在一個數據集上的一次動態執行過程
(2)電腦可以多個程序同時運行,如一邊聽着音樂,一邊用QQ聊天;一個程序的運行可能有多個進程,如多個QQ同時在線,一個;我們在資源管理器裏可以看到,某個軟件有很多個進程。QQ進行多窗口聊天並截圖。
(3)但其實它們並不是同時進行的,因爲一個電腦只有一個CPU,而一個CPU不可能滿足多個程序同時運行,之所以給我們看到它們是同時運行,是因爲CPU處理速度太快了,可以達到納秒(10的-9次方)級別,它運行程序時就可以瞬間切換,一會兒運行這個一會兒運行那個,當然,它們都是按照規則來運行。雖然一直在切換,由於速度特別快,因此給我們感覺是CPU在同時運行多個程序,一種併發效果。
(4)不過,雖然它能達到一種併發運行程序的效果,但是這也是有極限的,這就是爲什麼當我們運行的程序太多的時候就會出現卡頓的原因之一。到現在,我們已經有了雙核、四核、八核CPU,就相當於有多個CPU同時轉,讓我們的電腦或手機更加流暢,說白了就是我們更加無法察覺出它運行程序的瞬間切換。
6、線程的本質
(1)本質:線程是進程的一個最小執行單元,進程之下有多個線程,一個進程至少有一個線程。
(2)如某個軟件下有多個功能,而我們運行某一項功能就是打開一個線程,而該軟件運行着,就是一個大的進程。如在QQ裏面邊聊天,同時接受郵件,同時使用截圖。
(3)進程與線程的區別:進程是最小的資源單位,系統分配內存是分給進程的;線程是最小的執行單元,但不可能只有線程存在,因爲它需要進程的資源這個前提。
threading線程模塊
1、開三個線程
(1)參考代碼:
import threading #導入線程模塊
import time
def Hi(num):
print('hello python %d\n' %num)
time.sleep(3) #睡3秒
if __name__ == '__main__':
t1 = threading.Thread(target=Hi,args=(10,))
#實例化一個線程對象;傳入一個函數名,不能加();再傳入函數需要的參數到args中
t1.start() #啓動一個線程對象
t1 = threading.Thread(target=Hi,args=(9,)) #線程二
t1.start() #啓動一個線程對象
print('運行結束!')
(2)運行原理:
(3)參考結果:
2、線程對象的 join 方法
就是等待效果,對子線程使用
(1)不加join的情況:
import threading
import time
def game():
print('%s開始玩遊戲...' %time.ctime())
time.sleep(3)
print('%s結束停止玩遊戲。' %time.ctime())
def music():
print('\n%s開始播放音樂...'%time.ctime())
time.sleep(5)
print('%s結束播放音樂。' %time.ctime())
if __name__ == '__main__':
t1 = threading.Thread(target=game) #實例化線程對象
t1.start() #執行線程對象
t2 = threading.Thread(target=music) #實例化線程對象
t2.start() #執行線程對象
#效果:開始玩遊戲、開始播放音樂、程序結束先出來;
#再停頓3秒,出停止玩遊戲,再停2秒,出結束播放,程序一共執行5秒
print('\n程序結束!')
(2)不加join結果:
(3)將if name == ‘main’:的內容換成以下:
if __name__ == '__main__':
t1 = threading.Thread(target=game) #實例化線程對象
t1.start() #執行線程對象
t2 = threading.Thread(target=music) #實例化線程對象
t2.start() #執行線程對象
t1.join() #加了一個join就會按照順序來執行,等子線程執行完了再回到主線程
#t2.join()
print('\n程序結束!')
(4)加join的結果:
(5)若將join處的語句改成
t1.join() #加了一個join就會按照順序來執行,等子線程執行完了再回到主線程
#t2.join()
則會先出兩個開始,睡3秒,再出停止遊戲,和程序結束,等2秒,出接收播放音樂
3、繼承式調用
import threading
import time
class MyThread(threading.Thread):
def __init__(self,num):
threading.Thread.__init__(self)
self.num = num
def run(self):#定義每個線程要運行的函數
print("running on number:%s" %self.num)
time.sleep(3)
if __name__ == '__main__':
t1 = MyThread(1)
t2 = MyThread(2)
t1.start()
t2.start()
print("ending......")
繼承式調用的用處並不大,也非常少用到,這裏只簡單介紹一下
4、setDamen()守護線程
將子線程跟隨主線程的結束而結束,就是一旦回到主線程,子線程的後續動作都不會執行了一定要放在start之前用
參考代碼:
import threading
import time
threads = [] #用來裝子線程的空列表
def game():
print('\n%s開始玩遊戲...' %time.ctime())
time.sleep(3)
print('%s結束停止玩遊戲。' %time.ctime())
def prograss():
print('\n%s start to write prograss...' %time.ctime())
time.sleep(5)
print('%s finish to write prograss.' %time.ctime())
#實例化2個線程對象
t1 = threading.Thread(target=game)
t2 = threading.Thread(target=prograss)
#追加2個對象到列表中
threads.append(t1)
threads.append(t2)
if __name__ == '__main__':
t1.setDaemon(True)
for t in threads:
#t.setDaemon(True) #必須放在start之前
t.start()
print('\n程序結束!%s' %time.ctime())
5、threading下的其他方法
run(): 線程被cpu調度後自動執行線程對象的run方法
start(): 啓動線程活動
isAlive(): 返回線程是否活動的
getName(): 返回線程名
setName(): 設置線程名
threading.currentThread(): 返回當前的線程變量
threading.enumerate(): 返回一個包含正在運行的線程的list。正在運行指線程啓動後、結束前,不包括啓動前和終止後的線程
threading.activeCount(): 返回正在運行的線程數量,與len(threading.enumerate())有相同的結果