python的執行緒和程序
1、執行緒的基本概念
概念
執行緒是程序中執行運算的最小單位,是程序中的一個實體,是被系統獨立排程和分派的基本單位,執行緒自己不擁有系統資源,只擁有一點在執行中必不可少的資源,但它可與同屬一個程序的其它執行緒共享程序所擁有的全部資源。一個執行緒可以建立和撤消另一個執行緒,同一程序中的多個執行緒之間可以併發執行。
************************
好處
-
(1)易於排程。
-
(2)提高併發性。通過執行緒可方便有效地實現併發性。程序可建立多個執行緒來執行同一程式的不同部分。
-
(3)開銷少。建立執行緒比建立程序要快,所需開銷很少
執行緒常用方法
方法 | 註釋 |
---|---|
start() | 執行緒準備就緒,等待CPU排程 |
setName() | 為執行緒設定名稱 |
getName() | 獲取執行緒名稱 |
setDaemon(True) | 設定為守護執行緒 |
join() | 逐個執行每個執行緒,執行完畢後繼續往下執行 |
run() | 執行緒被cpu排程後自動執行執行緒物件的run方法,如果想自定義執行緒類,直接重寫run方法就行了 |
2、程序的基本狀態及狀態之間的關係
狀態:執行、阻塞、掛起阻塞、就緒、掛起就緒
狀態之間的轉換:
-
(1)準備就緒的程序,被CPU排程執行,變成執行態;
-
(2)執行中的程序,進行I/O請求或者不能得到所請求的資源,變成阻塞態;
-
(3)執行中的程序,程序執行完畢(或時間片已到),變成就緒態;
-
(4)將阻塞態的程序掛起,變成掛起阻塞態,當導致程序阻塞的I/O操作在使用者重啟程序前完成(稱之為喚醒),掛起阻塞態變成掛起就緒態,當用戶在I/O操作結束之前重啟程序,掛起阻塞態變成阻塞態;
-
(5)將就緒(或執行)中的程序掛起,變成掛起就緒態,當該程序恢復之後,掛起就緒態變成就緒態;
3、執行緒和程序的關係以及區別?
“電腦CPU有幾個核心,就同時只能執行幾個任務。沒有真正意義上的併發,所謂見到的併發只是通過上下文切換,CPU執行速度快產生的,我們只是感覺不到。
全域性直譯器鎖GIL限制了python用不了多個CPU,只能用一個CPU核心,防止多執行緒時資料錯亂。”
** 程序和執行緒的關係:**
-
(1)一個執行緒只能屬於一個程序,而一個程序可以有多個執行緒,但至少有一個執行緒。
-
(2)資源分配給程序,同一程序的所有執行緒共享該程序的所有資源。
-
(3)處理機分給執行緒,即真正在處理機上執行的是執行緒
-
(4)執行緒在執行過程中,需要協作同步。不同程序的執行緒間要利用訊息通訊的辦法實現同步。執行緒是指程序內的一個執行單元,也是程序內的可排程實體.
程序與執行緒的區別:
-
(1)排程:執行緒作為排程和分配的基本單位,程序作為擁有資源的基本單位
-
(2)併發性:不僅程序之間可以併發執行,同一個程序的多個執行緒之間也可併發執行
-
(3)擁有資源:程序是擁有資源的一個獨立單位,執行緒不擁有系統資源,但可以訪問隸屬於程序的資源.
-
(4)系統開銷:在建立或撤消程序時,由於系統都要為之分配和回收資源,導致系統的開銷明顯大於建立或撤消執行緒時的開銷。
程序與執行緒的詳解:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014319272686365ec7ceaeca33428c914edf8f70cca383000
4、程序間通訊的方式?
-
(1)管道(pipe)及有名管道(named pipe):管道可用於具有親緣關係的父子程序間的通訊,有名管道除了具有管道所具有的功能外,它還允許無親緣關係程序間的通訊。
-
(2)訊號(signal):訊號是在軟體層次上對中斷機制的一種模擬,它是比較複雜的通訊方式,用於通知程序有某事件發生,一個程序收到一個訊號與處理器收到一箇中斷請求效果上可以說是一致的。
-
(3)訊息佇列(message queue):訊息佇列是訊息的連結表,它克服了上兩種通訊方式中訊號量有限的缺點,具有寫許可權得程序可以按照一定得規則向訊息佇列中新增新資訊;對訊息佇列有讀許可權得程序則可以從訊息佇列中讀取資訊。
-
(4)共享記憶體(shared memory):可以說這是最有用的程序間通訊方式。它使得多個程序可以訪問同一塊記憶體空間,不同程序可以及時看到對方程序中對共享記憶體中資料得更新。這種方式需要依靠某種同步操作,如互斥鎖和訊號量等。
-
(5)訊號量(semaphore):主要作為程序之間及同一種程序的不同執行緒之間得同步和互斥手段。
-
(6)套接字(socket):這是一種更為一般得程序間通訊機制,它可用於網路中不同機器之間的程序間通訊,應用非常廣泛。
5、同步和互斥的區別:
-
當有多個執行緒的時候,經常需要去同步這些執行緒以訪問同一個資料或資源。例如,假設有一個程式,其中一個執行緒用於把檔案讀到記憶體,而另一個執行緒用於統計檔案中的字元數。當然,在把整個檔案調入記憶體之前,統計它的計數是沒有意義的。但是,由於每個操作都有自己的執行緒,作業系統會把兩個執行緒當作是互不相干的任務分別執行,這樣就可能在沒有把整個檔案裝入記憶體時統計字數。為解決此問題,你必須使兩個執行緒同步工作。
-
所謂同步,是指散步在不同程序之間的若干程式片斷,它們的執行必須嚴格按照規定的某種先後次序來執行,這種先後次序依賴於要完成的特定的任務。如果用對資源的訪問來定義的話,同步是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源。
-
所謂互斥,是指散佈在不同程序之間的若干程式片斷,當某個程序執行其中一個程式片段時,其它程序就不能執行它們之中的任一程式片段,只能等到該程序執行完這個程式片段後才可以執行。如果用對資源的訪問來定義的話,互斥某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。
6、例項
#原來用的單執行緒執行的方式:序列,執行完一個在繼續下一個 import time def run(): time.sleep(1) #sleep 1秒 print('run...') start_time=time.time() #獲取開始執行時間 for i in range(5): run() end_time=time.time() print(end_time-start_time) #以上程式碼會每隔1秒輸出一次run... ,總執行時間是5秒多5.070008754730225
#啟動多執行緒,並用t.join()方法,讓主執行緒等待所有子執行緒執行完成後,在繼續往下走程式碼 import threading #threading模組用於啟動執行緒 from threading import Thread import time def run(): time.sleep(1) #sleep 1秒 print('run...') start_time=time.time() #獲取開始執行時間 # threading.Thread() # 沒匯入這個時的寫法:from threading import Thread threads=[] #建立一個空執行緒列表,用於存放每個子執行緒的 for i in range(20):#迴圈啟動20個執行緒 t1=Thread(target=run,) #啟動執行緒,多執行緒同時執行,thread啟動的都是子執行緒,子執行緒執行run方法 threads.append(t1) #將產生的子執行緒存入到執行緒list中 t1.start() #執行 for t in threads: #迴圈獲取所有子執行緒 t.join() #主執行緒等待所有子執行緒執行結束之後再繼續向下走程式碼 end_time=time.time() #獲取執行結束的時間 print(end_time-start_time) #以上程式碼會同時輸出20個run...,總執行時間是1秒多1.029601812362671
#啟動多執行緒,效果如上面的程式碼,只是用判斷active_count()替代了上面的.join()方法 from threading import Thread import time def run(): time.sleep(1) #sleep 1秒 print('run...') start_time=time.time() #獲取開始執行時間 for i in range(20):#迴圈啟動20個執行緒 t1=Thread(target=run,) #啟動執行緒,多執行緒同時執行,thread啟動的都是子執行緒,子執行緒執行run方法 t1.start() #執行 while threading.active_count()!=1: #判斷子執行緒數是否執行結束,active_count()表示當前活動執行緒數 pass end_time=time.time() #獲取執行結束的時間 print(end_time-start_time)
#以上程式碼會同時輸出20個run...,總執行時間是1秒多1.029601812362671
多執行緒下載圖片例項:
import requests,time,threading from hashlib import md5 res=[] #為多執行緒拿到返回值使用,否則多執行緒無法拿到返回值 def down_load_pic(url): #下載圖片的url r=requests.get(url) file_name=md5(r.content).hexdigest() #把檔案md5之後的字串當做檔名 with open(file_name+'.jpg','wb') as fw: fw.write(r.content) print('%s下載完成'%file_name) res.append(file_name) return file_name urls=[ 'http://p2.so.qhimgs1.com/bdr/_240_/t0128ebf20d8a62ed1e.jpg', 'http://p1.so.qhimgs1.com/bdr/_240_/t01a44596706ed343dd.jpg', 'http://p2.so.qhimgs1.com/bdr/_240_/t01a04afe4928109a2e.jpg', 'http://p0.so.qhmsg.com/bdr/_240_/t01c301b9d7753c1c4f.jpg', 'http://p2.so.qhimgs1.com/bdr/_240_/t01b53912c4720a714e.jpg', 'http://p2.so.qhimgs1.com/bdr/_240_/t01548810ae247dbad2.jpg' ] start_time=time.time() # #單執行緒下載 # for url in urls: # down_load_pic(url) #多執行緒下載 for url in urls: t=threading.Thread(target=down_load_pic,args=(url,))#因為down_load_pic函式有引數,所以要寫args傳參,args=(url,):當只有一個引數時,後面要加逗號 t.start() while threading.active_count()!=1: pass end_time=time.time() print('下載完成,下載時間是%s'%(end_time-start_time)) print(res) #返回list,存放所有file_nameView Code
7. 守護執行緒
守護主執行緒,使用setDaemon(True)把所有的子執行緒都變成了主執行緒的守護執行緒,主執行緒執行完成之後,子執行緒立即結束。所以當主執行緒結束後,整個程式就退出了。
import threading import time def run(): time.sleep(5) print('run...') for i in range(100): puren = threading.Thread(target=run) puren.setDaemon(True)#設定子執行緒為守護執行緒 puren.start() print('over') #以上程式碼輸出結果只有一個:over
8. 鎖:Lock
是在多個執行緒同時去操作同一個資料的時候,可能會導致資料不正確。要把這個資料機加上鎖,這樣同時就只能有一個執行緒在操作這個資料了。
python2上需要手動加鎖,python3不用了,程式會自動給加鎖了
import threading count=0 lock=threading.Lock() #申請一把鎖 def run(): global count with lock: #等同於下方註釋的3行程式碼 count+=1 # lock.acquire() #加上鎖 # count+=1 # lock.release() #釋放,如果不釋放就會產生死鎖,一直等著 for i in range(10): t=threading.Thread(target=run) t.start() while threading.active_count()!=1: pass print(count) #輸出結果是10
<執行緒鎖與GIL的區別>:
- GIL是防止C語言的原生執行緒執行時互相沖掉資料,因資料是共享的;
- 執行緒鎖是防止python程式碼執行時(在作業系統之上)互相沖掉資料;
- 因此在多執行緒中如果要同時修改同一資料,就要加鎖
9. 多程序
import multiprocessing import time def run(): time.sleep(10) print('run..') if __name__=='__main__': for i in range(10): p=multiprocessing.Process(target=run) p.start() while multiprocessing.active_children():#活動的子程序,等待其他子程序執行結束 pass print(multiprocessing.cpu_count())#檢視cpu的個數
多執行緒:
無法利用多個CPU核心
多程序:
可以利用多個CPU的核心
--------------------------------
如何選取多執行緒還是多程序:
IO密集型任務
IO消耗比較多的
適合用多執行緒
input output
磁碟IO
網路IO
CPU密集型任務
消耗CPU比較多的
適合用多程序
多執行緒與多程序參考部落格:https://www.cnblogs.com/whatisfantasy/p/6440585.html