python多線程超詳細詳解
更新時間:2021年04月07日 16:48:36 作者:笨小孩哈哈
這篇文章主要介紹了python多線程超詳細詳解,多線程這個知識點非常重要,想了解的同學可以參考下
python中的多線程是一個非常重要的知識點,今天為大家對多線程進行詳細的說明,代碼中的注釋有多線程的知識點還有測試用的實例。
import threading from threading import Lock,Thread import time,os ''' python多線程詳解 什么是線程? 線程也叫輕量級進程,是操作系統(tǒng)能夠進行運算調(diào)度的最小單位,它被包涵在進程之中,是進程中的實際運作單位。 線程自己不擁有系統(tǒng)資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其他線程共享進程所 擁有的全部資源。一個線程可以創(chuàng)建和撤銷另一個線程,同一個進程中的多個線程之間可以并發(fā)執(zhí)行 ''' ''' 為什么要使用多線程? 線程在程序中是獨立的、并發(fā)的執(zhí)行流。與分隔的進程相比,進程中線程之間的隔離程度要小,它們共享內(nèi)存、文件句柄 和其他進程應有的狀態(tài)。 因為線程的劃分尺度小于進程,使得多線程程序的并發(fā)性高。進程在執(zhí)行過程之中擁有獨立的內(nèi)存單元,而多個線程共享 內(nèi)存,從而極大的提升了程序的運行效率。 線程比進程具有更高的性能,這是由于同一個進程中的線程都有共性,多個線程共享一個進程的虛擬空間。線程的共享環(huán)境 包括進程代碼段、進程的共有數(shù)據(jù)等,利用這些共享的數(shù)據(jù),線程之間很容易實現(xiàn)通信。 操作系統(tǒng)在創(chuàng)建進程時,必須為改進程分配獨立的內(nèi)存空間,并分配大量的相關資源,但創(chuàng)建線程則簡單得多。因此,使用多線程 來實現(xiàn)并發(fā)比使用多進程的性能高得要多。 ''' ''' 總結起來,使用多線程編程具有如下幾個優(yōu)點: 進程之間不能共享內(nèi)存,但線程之間共享內(nèi)存非常容易。 操作系統(tǒng)在創(chuàng)建進程時,需要為該進程重新分配系統(tǒng)資源,但創(chuàng)建線程的代價則小得多。因此使用多線程來實現(xiàn)多任務并發(fā)執(zhí)行比使用多進程的效率高 python語言內(nèi)置了多線程功能支持,而不是單純地作為底層操作系統(tǒng)的調(diào)度方式,從而簡化了python的多線程編程。 ''' ''' 普通創(chuàng)建方式 ''' # def run(n): # print('task',n) # time.sleep(1) # print('2s') # time.sleep(1) # print('1s') # time.sleep(1) # print('0s') # time.sleep(1) # # if __name__ == '__main__': # t1 = threading.Thread(target=run,args=('t1',)) # target是要執(zhí)行的函數(shù)名(不是函數(shù)),args是函數(shù)對應的參數(shù),以元組的形式存在 # t2 = threading.Thread(target=run,args=('t2',)) # t1.start() # t2.start() ''' 自定義線程:繼承threading.Thread來定義線程類,其本質是重構Thread類中的run方法 ''' # class MyThread(threading.Thread): # def __init__(self,n): # super(MyThread,self).__init__() #重構run函數(shù)必須寫 # self.n = n # # def run(self): # print('task',self.n) # time.sleep(1) # print('2s') # time.sleep(1) # print('1s') # time.sleep(1) # print('0s') # time.sleep(1) # # if __name__ == '__main__': # t1 = MyThread('t1') # t2 = MyThread('t2') # t1.start() # t2.start() ''' 守護線程 下面這個例子,這里使用setDaemon(True)把所有的子線程都變成了主線程的守護線程, 因此當主線程結束后,子線程也會隨之結束,所以當主線程結束后,整個程序就退出了。 所謂'線程守護',就是主線程不管該線程的執(zhí)行情況,只要是其他子線程結束且主線程執(zhí)行完畢,主線程都會關閉。也就是說:主線程不等待該守護線程的執(zhí)行完再去關閉。 ''' # def run(n): # print('task',n) # time.sleep(1) # print('3s') # time.sleep(1) # print('2s') # time.sleep(1) # print('1s') # # if __name__ == '__main__': # t=threading.Thread(target=run,args=('t1',)) # t.setDaemon(True) # t.start() # print('end') ''' 通過執(zhí)行結果可以看出,設置守護線程之后,當主線程結束時,子線程也將立即結束,不再執(zhí)行 ''' ''' 主線程等待子線程結束 為了讓守護線程執(zhí)行結束之后,主線程再結束,我們可以使用join方法,讓主線程等待子線程執(zhí)行 ''' # def run(n): # print('task',n) # time.sleep(2) # print('5s') # time.sleep(2) # print('3s') # time.sleep(2) # print('1s') # if __name__ == '__main__': # t=threading.Thread(target=run,args=('t1',)) # t.setDaemon(True) #把子線程設置為守護線程,必須在start()之前設置 # t.start() # t.join() #設置主線程等待子線程結束 # print('end') ''' 多線程共享全局變量 線程時進程的執(zhí)行單元,進程時系統(tǒng)分配資源的最小執(zhí)行單位,所以在同一個進程中的多線程是共享資源的 ''' # g_num = 100 # def work1(): # global g_num # for i in range(3): # g_num+=1 # print('in work1 g_num is : %d' % g_num) # # def work2(): # global g_num # print('in work2 g_num is : %d' % g_num) # # if __name__ == '__main__': # t1 = threading.Thread(target=work1) # t1.start() # time.sleep(1) # t2=threading.Thread(target=work2) # t2.start() ''' 由于線程之間是進行隨機調(diào)度,并且每個線程可能只執(zhí)行n條執(zhí)行之后,當多個線程同時修改同一條數(shù)據(jù)時可能會出現(xiàn)臟數(shù)據(jù), 所以出現(xiàn)了線程鎖,即同一時刻允許一個線程執(zhí)行操作。線程鎖用于鎖定資源,可以定義多個鎖,像下面的代碼,當需要獨占 某一個資源時,任何一個鎖都可以鎖定這個資源,就好比你用不同的鎖都可以把這個相同的門鎖住一樣。 由于線程之間是進行隨機調(diào)度的,如果有多個線程同時操作一個對象,如果沒有很好地保護該對象,會造成程序結果的不可預期, 我們因此也稱為“線程不安全”。 為了防止上面情況的發(fā)生,就出現(xiàn)了互斥鎖(Lock) ''' # def work(): # global n # lock.acquire() # temp = n # time.sleep(0.1) # n = temp-1 # lock.release() # # # if __name__ == '__main__': # lock = Lock() # n = 100 # l = [] # for i in range(100): # p = Thread(target=work) # l.append(p) # p.start() # for p in l: # p.join() ''' 遞歸鎖:RLcok類的用法和Lock類一模一樣,但它支持嵌套,在多個鎖沒有釋放的時候一般會使用RLock類 ''' # def func(lock): # global gl_num # lock.acquire() # gl_num += 1 # time.sleep(1) # print(gl_num) # lock.release() # # # if __name__ == '__main__': # gl_num = 0 # lock = threading.RLock() # for i in range(10): # t = threading.Thread(target=func,args=(lock,)) # t.start() ''' 信號量(BoundedSemaphore類) 互斥鎖同時只允許一個線程更改數(shù)據(jù),而Semaphore是同時允許一定數(shù)量的線程更改數(shù)據(jù),比如廁所有3個坑, 那最多只允許3個人上廁所,后面的人只能等里面有人出來了才能再進去 ''' # def run(n,semaphore): # semaphore.acquire() #加鎖 # time.sleep(3) # print('run the thread:%s\n' % n) # semaphore.release() #釋放 # # # if __name__== '__main__': # num=0 # semaphore = threading.BoundedSemaphore(5) #最多允許5個線程同時運行 # for i in range(22): # t = threading.Thread(target=run,args=('t-%s' % i,semaphore)) # t.start() # while threading.active_count() !=1: # pass # else: # print('----------all threads done-----------') ''' python線程的事件用于主線程控制其他線程的執(zhí)行,事件是一個簡單的線程同步對象,其主要提供以下的幾個方法: clear將flag設置為 False set將flag設置為 True is_set判斷是否設置了flag wait會一直監(jiān)聽flag,如果沒有檢測到flag就一直處于阻塞狀態(tài) 事件處理的機制:全局定義了一個Flag,當Flag的值為False,那么event.wait()就會阻塞,當flag值為True, 那么event.wait()便不再阻塞 ''' event = threading.Event() def lighter(): count = 0 event.set() #初始者為綠燈 while True: if 5 < count <=10: event.clear() #紅燈,清除標志位 print("\33[41;lmred light is on...\033[0m]") elif count > 10: event.set() #綠燈,設置標志位 count = 0 else: print('\33[42;lmgreen light is on...\033[0m') time.sleep(1) count += 1 def car(name): while True: if event.is_set(): #判斷是否設置了標志位 print('[%s] running.....'%name) time.sleep(1) else: print('[%s] sees red light,waiting...'%name) event.wait() print('[%s] green light is on,start going...'%name) # startTime = time.time() light = threading.Thread(target=lighter,) light.start() car = threading.Thread(target=car,args=('MINT',)) car.start() endTime = time.time() # print('用時:',endTime-startTime) ''' GIL 全局解釋器 在非python環(huán)境中,單核情況下,同時只能有一個任務執(zhí)行。多核時可以支持多個線程同時執(zhí)行。但是在python中,無論有多少個核 同時只能執(zhí)行一個線程。究其原因,這就是由于GIL的存在導致的。 GIL的全程是全局解釋器,來源是python設計之初的考慮,為了數(shù)據(jù)安全所做的決定。某個線程想要執(zhí)行,必須先拿到GIL,我們可以 把GIL看做是“通行證”,并且在一個python進程之中,GIL只有一個。拿不到線程的通行證,并且在一個python進程中,GIL只有一個, 拿不到通行證的線程,就不允許進入CPU執(zhí)行。GIL只在cpython中才有,因為cpython調(diào)用的是c語言的原生線程,所以他不能直接操 作cpu,而只能利用GIL保證同一時間只能有一個線程拿到數(shù)據(jù)。而在pypy和jpython中是沒有GIL的 python在使用多線程的時候,調(diào)用的是c語言的原生過程。 ''' ''' python針對不同類型的代碼執(zhí)行效率也是不同的 1、CPU密集型代碼(各種循環(huán)處理、計算等),在這種情況下,由于計算工作多,ticks技術很快就會達到閥值,然后出發(fā)GIL的 釋放與再競爭(多個線程來回切換當然是需要消耗資源的),所以python下的多線程對CPU密集型代碼并不友好。 2、IO密集型代碼(文件處理、網(wǎng)絡爬蟲等設計文件讀寫操作),多線程能夠有效提升效率(單線程下有IO操作會進行IO等待, 造成不必要的時間浪費,而開啟多線程能在線程A等待時,自動切換到線程B,可以不浪費CPU的資源,從而能提升程序的執(zhí)行 效率)。所以python的多線程對IO密集型代碼比較友好。 ''' ''' 主要要看任務的類型,我們把任務分為I/O密集型和計算密集型,而多線程在切換中又分為I/O切換和時間切換。如果任務屬于是I/O密集型, 若不采用多線程,我們在進行I/O操作時,勢必要等待前面一個I/O任務完成后面的I/O任務才能進行,在這個等待的過程中,CPU處于等待 狀態(tài),這時如果采用多線程的話,剛好可以切換到進行另一個I/O任務。這樣就剛好可以充分利用CPU避免CPU處于閑置狀態(tài),提高效率。但是 如果多線程任務都是計算型,CPU會一直在進行工作,直到一定的時間后采取多線程時間切換的方式進行切換線程,此時CPU一直處于工作狀態(tài), 此種情況下并不能提高性能,相反在切換多線程任務時,可能還會造成時間和資源的浪費,導致效能下降。這就是造成上面兩種多線程結果不能的解釋。 結論:I/O密集型任務,建議采取多線程,還可以采用多進程+協(xié)程的方式(例如:爬蟲多采用多線程處理爬取的數(shù)據(jù));對于計算密集型任務,python此時就不適用了。 '''
到此這篇關于python多線程超詳細詳解的文章就介紹到這了,更多相關python多線程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Python使用itcaht庫實現(xiàn)微信自動收發(fā)消息功能
這篇文章主要介紹了Python使用itcaht庫實現(xiàn)微信自動收發(fā)消息功能,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07