定義:
- 進程:資源的集合,一個程序就是一個進程。
- 線程:一個程序最小的運行單位。
import threading #引入線程模塊 import time def run(): time.sleep(5) print('over') start_time=time.time() run() run() run() run() end_time=time.time() print('run_time', end_time-start_time) #結果:run_time=20.38954234234
上面這個例子是單線程執行的,運行時間是20多秒,如果使用多線程,則會並行執行,執行結果應該是5秒左右。
-
主線程等待子線程
方法一:想要讓主線程等待添加的線程,需要先把創建的線程統一放到list里面,循環執行完,使用.join()方法,如下:
import threading import time def run(): time.sleep(5) print('over') start_time=time.time() thread_list = [] for i in range(5): t=threading.Thread(target=run) #實例化一個線程,target代表要指定執行什么,例如:target=run就是執行run()這個函數,指定函數名,不要加() #函數里面如果有參數,可以加上args,如:t=threading.Thread(target=run,args[1,2,3,]) ,有幾個參數,就在[]里面寫幾個參數 t.start() #啟動這個線程 thread_list.append(t) #把線程放到list里面 for thread in thread_list: thread.join() #主線程等待子線程 end_time=time.time() print('run_time=', end_time-start_time)
方法二:每一次循環都會判斷下剩余的線程是不是只剩下1個,不是一個的話,繼續循環,直到剩下的線程數為1時,繼續往下執行,如下:
import threading import time threads = [] start_time2 = time.time() def insert_db(): time.sleep(3) print('insert_db over') for i in range(3): t = threading.Thread(target=insert_db) t.start() while threading.activeCount()!=1: #每一次循環都會判斷下剩余的線程是不是只剩下1個,不是一個的話,繼續循環,直到剩下的線程數為1時,繼續往下執行 pass end_time2 = time.time() print('多線程執行的時間',end_time2 - start_time2) print('鎖門...')
-
線程 單個啟動 & 同時啟動
import threading '''單個啟動''' for i in range(10): t = threading.Thread(target=func,args=[1]) t.start()#start()在這里是一個一個線程拿過來,單個啟動 '''同時啟動啟動''' ts = [] for i in range(10): t = threading.Thread(target=func,args=[1]) ts.append(t)#先10個線程放到一個list里面 for t in ts: t.start() #循環同時啟動
練習分析:
import threading import time def insert_db(): time.sleep(3) print('insert_db over') start_time = time.time() for i in range(3): t = threading.Thread(target=insert_db) t.start() end_time = time.time() print('多線程執行的時間',end_time - start_time) 結果: 多線程執行的時間 0.0010001659393310547 insert_db over insert_db over insert_db over
我們看到的結果是中運行時間是0.0010001659393310547,還不到一秒鍾,而我們代碼所設置的是最少也會需要3秒鍾,為什么時間會是這么短呢?
——因為所得到的時間只是主線程執行完它的工作時間,並不包括子線程執行的額時間。
壓測練習:
import threading import requests def request(): while True: r = requests.get('http://api.nnzhp.cn/api/user/stu_info?stu_name=%E7%8E%8B%E5%B0%8F%E6%9C%88') print(r.json()) for i in range(10): t = threading.Thread(target=request) t.start()
運行后會一直死循環,一直壓測,可以加入壓測的時間
單線程下載網頁:
import requests,time,threading from hashlib import md5 result_list = {} def down_load_pic(url): req = requests.get(url) m = md5(url.encode()) #把url轉換為二進制md5下 file_name = m.hexdigest()+'.png' #拼接文件名 with open(file_name ,'wb') as fw: fw.write(req.content) # return file_name result_list[file_name] = threading.current_thread() url_list = ['http://www.nnzhp.cn/wp-content/uploads/2019/10/f410afea8b23fa401505a1449a41a133.png', 'http://www.nnzhp.cn/wp-content/uploads/2019/11/481b5135e75c764b32b224c5650a8df5.png', 'http://www.nnzhp.cn/wp-content/uploads/2019/11/b23755cdea210cfec903333c5cce6895.png', 'http://www.nnzhp.cn/wp-content/uploads/2019/11/542824dde1dbd29ec61ad5ea867ef245.png'] start_time = time.time() for url in url_list: down_load_pic(url) end_time = time.time() print(end_time - start_time) #結果:4.439253807067871
多線程下載網頁:
import threading import requests import hashlib import time def down_load(url): name = hashlib.md5(url.encode()).hexdigest() #把url使用md5轉化為密文 r = requests.get(url) with open('%s.jpg'%name,'wb') as fw: fw.write(r.content) l = [ 'http://www.nnzhp.cn/wp-content/themes/QQ/images/logo.jpg', 'http://www.nnzhp.cn/wp-content/uploads/2016/12/2016aj5kn45fjk5-150x150.jpg', 'http://www.nnzhp.cn/wp-content/themes/QQ/images/thumbnail.png' ] for i in l: t = threading.Thread(target=down_load,args=[i]) #參數只有一個時,要使用()的方式來寫,需要多加一個逗號,如:args=(i,) # args是存參數的,如果里面只有一個參數的話,一定要在這個參數后面加一個逗號,因為是保存在元組里,如果不加逗號,它會默認為是字符串 應該寫成:args=(url,) t.start() while threading.activeCount()!=1: pass print('down load over...')
一個進程里面至少有一個線程,這個線程就是主線程。
主線程只是調度用的,它把子線程招來之后就完事了,因此如果要統計運行時間,必須要讓主線程等待所有的子線程都執行完后再記錄結束時間。
case_result = [] def run_case(case_name): print('run case..',case_name) case_result.append( {case_name:'success'}) #多線程運行函數時,函數的返回值是拿不到的,所以定義一個list #把函數運行的結果都存進去就ok了
-
查看當前有多少個線程:threading.activeCount()
import threading #引入線程模塊 import time def run(): time.sleep(1) for i in range(10): t = threading.Thread(target=run) t.start() print('當前有%s個線程'%threading.activeCount()) #當前有幾個線程 結果:當前有11個線程
為什么會是11個線程而不是10個線程呢?我們明明是循環啟動了10個線程,結果怎么就是11個了呢?
因為本身有一個主線程在運行,好比是我們自己在干活,我們又找了10個人來一起干活,加上我們自己一共是11個人在干活。所有的程序默認就會有一個線程。
2、守護線程
- 一旦主線程死掉,那么守護線程不管有沒執行完事,全部結束,相當於你是一個國王(主線程),你有很多仆人(守護線程),仆人都是為你服務的,一旦你死了,那么你的仆人都需要給你陪葬。
import threading import time def talk(name): print('正在和%s聊天'%name) time.sleep(200) print("qq主窗口") t = threading.Thread(target=talk,args=['劉一']) t.setDaemon(True) #設置線程為守護線程 t.start() time.sleep(5) print('結束。。。') 結果: qq主窗口 正在和劉一聊天 結束。。。
如果是沒有設置為守護線程,本身程序執行需要有200秒才可以結束,設置了守護線程后,主線程5秒到了就會結束,主線程結束了,剩下的正在執行的線程已經是守護線程了,不會繼續200秒結束,會立即跟隨主線程結束。
3、線程鎖
-
多個線程同時在操作同一個數據的時候,會有問題,就要把這個數據加個鎖,然后同一時間只能有一個線程操作這個數據了【多個人或是多線程在同時操作同一個數據時,可能會有問題,可以加下鎖】
- 忘記了解鎖或是代碼運行時沒有處理異常而沒有走到解鎖那里,都會成為線程死鎖。
舉例理解:比如說我們家里的衛生間,男女共用的,如果你進去時,沒有鎖門,有可能會有其他的開門,所以你需要進去時把門鎖一下,不用了,再把鎖打開下。
import threading from threading import Lock num = 0 lock = Lock()#申請一把鎖 def run(): global num lock.acquire(timeout = 3)#加鎖,timeout是指定下時間,可以不寫,如果指定了時間,超過后申請的鎖就會失效 num+=1 lock.release()#解鎖 lis = [] for i in range(5): t = threading.Thread(target=run) t.start() lis.append(t) for t in lis: t.join() print('over',num)
#多個線程操作同一個數據的時候,就得加鎖 import threading num = 0 lock = threading.Lock() #申請一把鎖,實例化一把鎖 def add(): global num # lock.acquire()#加鎖 # num+=1 # lock.release()#解鎖 #不解鎖,就會產生線程死鎖,會一直在等待 with lock:#簡寫,用with也會幫你加鎖,解鎖 num+=1 for i in range(20): t = threading.Thread(target=add,) t.start() while threading.activeCount() !=1: pass
-
查看當前哪個線程在執行:threading.current_thread()
import threading count = 0 lock = threading.Lock() def test(): global count print(threading.current_thread()) lock.acquire()#加鎖 count+=1 lock.release()#解鎖 #線程死鎖 for i in range(3): t = threading.Thread(target=test) t.start() 結果: <Thread(Thread-1, started 9000)> <Thread(Thread-2, started 4820)> <Thread(Thread-3, started 10224)>
下面來個簡單的爬蟲,看下多線程的效果:
import threading import requests, time urls = { "baidu": 'http://www.baidu.com', "blog": 'http://www.nnzhp.cn', "besttest": 'http://www.besttest.cn', "taobao": "http://www.taobao.com", "jd": "http://www.jd.com", } def run(name, url): res = requests.get(url) with open(name + '.html', 'w', encoding=res.encoding) as fw: fw.write(res.text) start_time = time.time() lis = [] for url in urls: t = threading.Thread(target=run, args=(url, urls[url])) t.start() lis.append(t) for t in lis: t.join() end_time = time.time() print('run time is %s' % (end_time - start_time)) #下面是單線程的執行時間 # start_time = time.time() # for url in urls: # run(url,urls[url]) # end_time = time.time() # print('run time is %s'%(end_time-start_time))
4、多進程,上面說了Python里面的多線程,是不能利用多核CPU的,如果想利用多核CPU的話,就得使用多進程,python中多進程使用multiprocessing模塊。
- 一個電腦有幾核的CPU,就只能同時運行幾個任務
——比如說我們的電腦是4核的CPU,只可以同時運行4個進程,但我們在實際使用中,如果是4核的CPU,運行的並不止是4個程序,這是因為CPU上下文切換的,這個程序運行完了,
會立即切換到另外一個運行,我們感覺不到。
import multiprocessing,time lock = multiprocessing.Lock() #申請一把鎖 a = 1 def down_load(): time.sleep(3) global a with lock: #使用with這個方法會自動的去判斷使用完了自動的關掉鎖釋放【with是自動管理上下文的,操作文件時也可以使用,會自動識別關閉】 a+=1 print("運行完了") if __name__ == '__main__': for i in range(5): p = multiprocessing.Process(target=down_load) # p=multiprocessing.Process(target=down_load,args =[1,2]) #有參數了可以加上args p.start() while len(multiprocessing.active_children())!=0:#等待子進程結束 pass print(multiprocessing.current_process()) print('end')
5、進程池
還可以使用進程池來快速啟動幾個進程,使用進程池的好處的就是他會自動管理進程數,咱們只需要給他設置一個最大的數就ok了。有新的請求提交到Pool中時,如果池還沒有滿,那么就會創建一個新的進程用來執行該請求;但如果池中的進程數已經達到指定的最大值,那么該請求就會等待,直到池中有進程結束,才會用之前的進程來執行新的任務。【數據大時使用】
from multiprocessing import Pool import os def worker(msg): print("%s開始執行,進程號為%d" % (msg,os.getpid())) if __name__ == '__main__': po = Pool(3) # 定義一個進程池,最大進程數3 for i in range(0, 10): # Pool().apply_async(要調用的目標,(傳遞給目標的參數元祖,)) # 每次循環將會用空閑出來的子進程去調用目標 po.apply_async(func=worker,args=(i,)) #第一個func參數指定運行的函數,第二個args是參數,沒有參數可以不寫 print("----start----") po.close() # 關閉進程池,關閉后po不再接收新的請求 po.join() # 等待po中所有子進程執行完成,必須放在close語句之后 print("-----end-----")
總結:
-
線程是用來干活的,只有進程的話是沒辦法運行的,進程里其實是線程在具體干活的。
-
線程和線程之間是互相獨立的。
-
線程是在進程里面
-
默認,一個進程里面只有一個進程
-
進程相當於是一個工廠,線程相當於是工廠里面具體干活的一個人
-
主線程,也就是程序一開始運行的時候,最初的那個線程
-
子線程,通過thread類實例化的線程,都是子線程
-
主線程等待子線程,執行結束后,主線程再去做別的操作
-
主線程執行完它自己工作的時間,並不包括子線程執行的時間
- Python里面的多線程利用不了多核的cpu,因為如果是在多個CPU上運行,運行的結果會不太一樣,所以加入一個全局解釋器鎖(GLI),保證線程都在同一個cpu運行
- 多進程可以利用多核CPU
- CPU密集型任務,用多進程 ->消耗的CPU比較多
- IO(磁盤IO、網絡IO)密集型任務,用多線程 ->消耗IO比較多【IO是上傳、下載的意思】
- 多線程,線程之間的的數據是共享的
- 多進程,每個進程之間的數據是獨立的,正是因為每個進程都是獨立的,所以多進程里面加鎖是沒任何意義的。
擴展:
協程,只有有個線程在運行,每遇到消耗IO的地方就會立馬切換,在切換到另一個,等着這個IO結束了,再去拿到數據,性能比較好;如:nginx就是使用的協程。
任何付出都是值得的,會越來越好