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
多線程下載圖片實例:
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_name
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
python的線程和進程