您好,登錄后才能下訂單哦!
本篇內容主要講解“怎么理解Python多線程”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么理解Python多線程”吧!
在實際處理數據時,因系統內存有限,我們不可能一次把所有數據都導出進行操作,所以需要批量導出依次操作。為了加快運行,我們會采用多線程的方法進行數據處理,以下為我總結的多線程批量處理數據的模板:
import threading # 從數據庫提取數據的類 class Scheduler(): def __init__(self): self._lock = threading.RLock() self.start = 0 # 每次取10000條數據 self.step = 10000 def getdata(self): # 上鎖,以免多線程同時對數據庫進行訪問,取出重復數據 self._lock.acquire() # 進行取數據操作 data = 'select * from table' \ 'where id between self.start and self.start + self.step' # 取完數據后,指針后移 self.start += self.step self._lock.release() return data # 處理數據的過程寫在這里 def processdata(): # 從該實例中提取數據 data = scheduler.getdata() while data: # 進行處理數據的具體操作: # 去重、補缺、運算...只要還有數據,本線程就繼續取新數據 # 然后再獲取數據,進行循環 data = scheduler.getdata() # 創建多線程,threads_num為創建的線程數 def threads_scheduler(threads_num): threads = [] for i in range(threads_num): # 創建線程 td = threading.Thread(target=processdata, name='th'+str(i+1)) threads.append(td) for t in threads: # 啟動線程 t.start() for t in threads: # 子線程守護 t.join() print('數據已全部處理成功') if __name__=='__main__': # 實例化一個調度器,初始化參數 scheduler = Scheduler() # 創建線程,開始處理數據 threads_scheduler(4)
主要分為三大部分:
Scheduler類,負責初始化參數,getdata方法負責提取數據
processdata方法中寫具體處理數據的流程
threads_scheduler方法負責創建線程
Python多線程的知識我分為4部分進行講解,以下帶大家來回顧重點:
本章先為大家介紹了線程的相關概念:
主線程:當一個程序啟動時,就有一個進程被操作系統(OS)創建,與此同時一個線程也立刻運行,該線程通常叫做程序的主線程(Main Thread)。因為它是程序開始時就執行的,如果你需要再創建線程,那么創建的線程就是這個主線程的子線程。
子線程:使用threading、ThreadPoolExecutor創建的線性均為子線程。
主線程的重要性體現在兩方面:1.是產生其他子線程的線程;2.通常它必須最后完成執行,比如執行各種關閉動作。
在飛車程序中,如果沒有多線程,我們就不能一邊聽歌一邊玩飛車,聽歌與玩游戲不能并行;在使用多線程后,我們就可以在玩游戲的同時聽背景音樂。在這個例子中啟動飛車程序就是一個進程,玩游戲和聽音樂是兩個線程。
Python提供了threading模塊來實現多線程:threading.Thread可以創建線程;setDaemon(True)為守護主線程,默認為False;join()為守護子線程。
from time import sleep import threading def music(music_name): for i in range(2): print('正在聽{}'.format(music_name)) sleep(1) print('music over') def game(game_name): for i in range(2): print('正在玩{}'.format(game_name)) sleep(3) print('game over') threads = [] t1 = threading.Thread(target=music,args=('稻香',)) threads.append(t1) t2 = threading.Thread(target=game,args=('飛車',)) threads.append(t2) if __name__ == '__main__': for t in threads: # t.setDaemon(True) t.start() for t in threads: t.join() print('主線程運行結束')
因為新建線程系統需要分配資源、終止線程系統需要回收資源,所以如果可以重用線程,則可以減去新建/終止的開銷以提升性能。同時,使用線程池的語法比自己新建線程執行線程更加簡潔。
Python為我們提供了ThreadPoolExecutor來實現線程池,此線程池默認子線程守護。它的適應場景為突發性大量請求或需要大量線程完成任務,但實際任務處理時間較短。
from time import sleep # fun為定義的待運行函數 with ThreadPoolExecutor(max_workers=5) as executor: ans = executor.map(fun, [遍歷值]) for res in ans: print(res) with ThreadPoolExecutor(max_workers=5) as executor: list = [遍歷值] ans = [executor.submit(fun, i) for i in list] for res in as_completed(ans): print(res.result())
其中max_workers為線程池中的線程個數,常用的遍歷方法有map和submit+as_completed。根據業務場景的不同,若我們需要輸出結果按遍歷順序返回,我們就用map方法,若想誰先完成就返回誰,我們就用submit+as_complete方法。
我們把一個時間段內只允許一個線程使用的資源稱為臨界資源,對臨界資源的訪問,必須互斥的進行。互斥,也稱間接制約關系。線程互斥指當一個線程訪問某臨界資源時,另一個想要訪問該臨界資源的線程必須等待。當前訪問臨界資源的線程訪問結束,釋放該資源之后,另一個線程才能去訪問臨界資源。鎖的功能就是實現線程互斥。
我把線程互斥比作廁所包間上大號的過程,因為包間里只有一個坑,所以只允許一個人進行大號。當第一個人要上廁所時,會將門上上鎖,這時如果第二個人也想大號,那就必須等第一個人上完,將鎖解開后才能進行,在這期間第二個人就只能在門外等著。這個過程與代碼中使用鎖的原理如出一轍,這里的坑就是臨界資源。
Python 的 threading 模塊引入了鎖。threading 模塊提供了 Lock 類,它有如下方法加鎖和釋放鎖:
acquire():對 Lock加鎖,其中timeout參數指定加鎖多少秒
release():釋放鎖
class Account: def __init__(self, card_id, balance): # 封裝賬戶ID、賬戶余額的兩個變量 self.card_id= card_id self.balance = balance def withdraw(account, money): # 進行加鎖 lock.acquire() # 賬戶余額大于取錢數目 if account.balance >= money: # 吐出鈔票 print(threading.current_thread().name + "取錢成功!吐出鈔票:" + str(money),end=' ') # 修改余額 account.balance -= money print("\t余額為: " + str(account.balance)) else: print(threading.current_thread().name + "取錢失敗!余額不足") # 進行解鎖 lock.release() # 創建一個賬戶,銀行卡id為8888,存款1000元 acct = Account("8888" , 1000) # 模擬兩個對同一個賬戶取錢 # 在主線程中創建一把鎖 lock = threading.Lock() threading.Thread(name='窗口A', target=withdraw , args=(acct , 800)).start() threading.Thread(name='窗口B', target=withdraw , args=(acct , 800)).start()
區別一:Lock被稱為原始鎖,一個線程只能請求一次;RLock被稱為重入鎖,可以被一個線程請求多次,即鎖中可以嵌套鎖。
import threading def main(): lock.acquire() print('第一道鎖') lock.acquire() print('第二道鎖') lock.release() lock.release() if __name__ == '__main__': lock = threading.Lock() main()
我們會發現這個程序只會打印“第一道鎖”,而且程序既沒有終止,也沒有繼續運行。這是因為Lock鎖在同一線程內第一次加鎖之后還沒有釋放時,就進行了第二次acquire請求,導致無法執行release,所以鎖永遠無法釋放,這就是死鎖。如果我們使用RLock就能正常運行,不會發生死鎖的狀態。
區別二:當Lock處于鎖定狀態時,不屬于特定線程,可在另一個線程中進行解鎖釋放;而RLock只有當前線程才能釋放本線程上的鎖,不可由其他線程進行釋放,所以在使用RLock時,acquire與release必須成對出現,即解鈴還須系鈴人。
import threading def main(): lock.release() print("在子線程解鎖后打印") if __name__ == '__main__': lock = threading.Lock() lock.acquire() t = threading.Thread(target=main) t.start()
在主線程中定義Lock鎖,然后上鎖,再創建一個子線程t運行main函數釋放鎖,結果正常輸出,說明主線程上的鎖,可由子線程解鎖。
如果把上面的鎖改為RLock則報錯。在實際中設計程序時,我們會將每個功能分別封裝成一個函數,每個函數中都可能會有臨界區域,所以就需要用到RLock。
import threading import time def fun_1(): print('開始') time.sleep(1) lock.acquire() print("第一道鎖") fun_2() lock.release() def fun_2(): lock.acquire() print("第二道鎖") lock.release() if __name__ == '__main__': lock = threading.RLock() t1 = threading.Thread(target=fun_1) t2 = threading.Thread(target=fun_1) t1.start() t2.start()
一句話總結就是Lock不能套娃,RLock可以套娃;Lock可以由其他線程中的鎖進行操作,RLock只能由本線程進行操作。
到此,相信大家對“怎么理解Python多線程”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。