python3進階篇(三)——多線程
前言:
閱讀這篇文章我能學到什麼?
一個應用程序就相當於一個進程,該進程創建時就具有一個主線程(內核線程),主線程可以創建其他子線程(用戶線程),當存在子線程時就形成了多線程。多線程可以使得運行程序在宏觀上同時執行多個任務,在一定程度上加快軟件執行速度。線程的操作涉及到:線程創建、同步、退讓、搶佔等。請閱讀這篇文章學習它。
——如果你覺得這是一篇不錯的博文,希望你能給一個小小的贊,感謝您的支持。
1 瞭解並創建線程
1.1 瞭解線程
當進程被創建時,操作系統將會爲它創建一個主線程,也即內核線程,注意它是操作系統創建的。用戶可以通過主線程創建子線程,或稱用戶線程。不論主線程還是子線程,每個獨立的線程都有一個程序的入口,對應在代碼中就是入口函數。一個進程可以有多個線程,線程是進程的執行單元。宏觀上各個線程可以是同時執行的,微觀上依然是CPU分時間片執行。Python3爲我們提供了操作線程的類,這簡化了線程開發的步驟。舊的線程模塊是_thread
,Python3提供了新的模塊’threading’操作線程。
1.2 創建線程
我們嘗試通過模塊’_thread’進行線程創建。
代碼示例:
import _thread
import time
def Run(ThreadName, Speed):
while True:
time.sleep(Speed) #延遲一定時間,單位爲s
print(ThreadName, "----", time.ctime(time.time()))
_thread.start_new_thread(Run, ("Thread-1", 3))
_thread.start_new_thread(Run, ("Thread-2", 5))
Run("ThreadMain", 4)
運行結果:
Thread-1 ---- Sun Jun 21 13:02:44 2020
ThreadMain ---- Sun Jun 21 13:02:45 2020
Thread-2 ---- Sun Jun 21 13:02:46 2020
Thread-1 ---- Sun Jun 21 13:02:47 2020
ThreadMain ---- Sun Jun 21 13:02:49 2020
Thread-1 ---- Sun Jun 21 13:02:50 2020
Thread-2 ---- Sun Jun 21 13:02:51 2020
ThreadMain ---- Sun Jun 21 13:02:53 2020
Thread-1 ---- Sun Jun 21 13:02:53 2020
Thread-2 ---- Sun Jun 21 13:02:56 2020
Thread-1 ---- Sun Jun 21 13:02:56 2020
ThreadMain ---- Sun Jun 21 13:02:57 2020
Thread-1 ---- Sun Jun 21 13:02:59 2020
該示例中總共有三個線程,函數start_new_thread()
用於創建線程並指定線程的入口函數,並且開始開始運行線程。最後別忘了主線程也在運行。從打印的時間信息可以看出,三個線程是各自獨立運行的。
1.3 創建線程類
通常爲了更好的封裝性和代碼重用性,我們會將線程類進行二次封裝。這裏我們嘗試通過模塊threading
封裝一個線程類。
代碼示例:
import threading
import time
class myThread(threading.Thread):
def __init__(self, Name, Speed):
threading.Thread.__init__(self) #調用父類構造函數
self.Name = Name
self.Speed = Speed
def run(self): #重載run函數
print("開始線程:", self.Name)
EntryFunction(self.Name, self.Speed)
print("結束子線程:", self.Name)
def EntryFunction(Name, Speed): #線程入口函數
for n in range(5):
time.sleep(Speed)
print(Name, "----", time.ctime(time.time()))
Thread1 = myThread("Thread-1", 3)
Thread2 = myThread("Thread-2", 5)
Thread1.start()
Thread2.start()
#等待子線程結束
Thread1.join()
Thread2.join()
print("主線程結束")
運行結果:
開始線程: Thread-1
開始線程: Thread-2
Thread-1 ---- Sun Jun 21 14:35:59 2020
Thread-2 ---- Sun Jun 21 14:36:01 2020
Thread-1 ---- Sun Jun 21 14:36:02 2020
Thread-1 ---- Sun Jun 21 14:36:05 2020
Thread-2 ---- Sun Jun 21 14:36:06 2020
Thread-1 ---- Sun Jun 21 14:36:08 2020
Thread-2 ---- Sun Jun 21 14:36:11 2020
Thread-1 ---- Sun Jun 21 14:36:11 2020
結束子線程: Thread-1
Thread-2 ---- Sun Jun 21 14:36:16 2020
Thread-2 ---- Sun Jun 21 14:36:21 2020
結束子線程: Thread-2
主線程結束
線程對象示例化後通過調用start()
函數可啓動線程,其將從線程run()
函數開始執行,我們重載了父類的run()
函數。join()
函數使得主線程會等待子線程結束。
2 線程同步
一些數據資源需要被多個線程操作,多個線程同時對同一塊數據修改,可能出現不可預料的結果。因爲線程操作數據是需要時間的,若在數據修改完成之前其他線程也進行修改則導致數據出現預料之外的情況。可以通過線程鎖實現線程同步,線程鎖有兩種狀態,即鎖定和未鎖定。
代碼示例:
import threading
import time
#1~5 5組數據及其有效標誌,Couter表示剩餘有效數據數,Length表示數據總個數
Data = {"Counter":5, "Length":5, 1:True, 2:True, 3:True, 4:True, 5:True} #用於測試的線程共享數據
ThreadLock = threading.Lock() #創建線程鎖
class myThread(threading.Thread):
def __init__(self, Name, Speed):
threading.Thread.__init__(self) #調用父類構造函數
self.Name = Name
self.Speed = Speed
def run(self): #重載run函數
print("開始線程:", self.Name)
GetData(self.Name, self.Speed)
print("結束子線程:", self.Name)
def GetData(Name, Speed): #線程入口函數
while Data["Counter"] > 0:
time.sleep(Speed)
print(Name, "----", time.ctime(time.time()))
ThreadLock.acquire() #獲取線程鎖,拿不到鎖則等待
for n in range(1, Data["Length"] + 1):
if Data[n] == True:
Data[n] = False
Data["Counter"] -= 1
print(Name, "----", "Get: ", n)
break
ThreadLock.release() #釋放線程鎖
Thread1 = myThread("Thread-1", 3)
Thread2 = myThread("Thread-2", 5)
Thread1.start()
Thread2.start()
Thread1.join()
Thread2.join()
print("主線程結束")
運行結果:
開始線程: Thread-1
開始線程: Thread-2
Thread-1 ---- Sun Jun 21 17:24:55 2020
Thread-1 ---- Get: 1
Thread-2 ---- Sun Jun 21 17:24:57 2020
Thread-2 ---- Get: 2
Thread-1 ---- Sun Jun 21 17:24:58 2020
Thread-1 ---- Get: 3
Thread-1 ---- Sun Jun 21 17:25:01 2020
Thread-1 ---- Get: 4
Thread-2 ---- Sun Jun 21 17:25:02 2020
Thread-2 ---- Get: 5
結束子線程: Thread-2
Thread-1 ---- Sun Jun 21 17:25:04 2020
結束子線程: Thread-1
主線程結束
我們創建了一個字典,字典內有5組數據,通過線程去將數據挨個置爲失效,爲了保證同一組數據不會被多個線程重複操作,並且是按順序失效這5組數據,我們需要給數據操作的代碼段加上線程鎖,以此來保證每次只能有一個線程操作這5組數據。acquire()
函數使得每次只有一個線程能獲得線程鎖,即進行後續的數據操作,其他線程則會等待,知道其他線程釋放鎖後並且自己獲得鎖,release()
函數使得線程釋放獲得的鎖。
3 線程優先級隊列
通過上面的線程鎖,我們不光可以實現一些數據單次只允許一個線程訪問,其他線程進行阻塞等待,我們也可以將一些代碼段的執行加上線程鎖,使得一些操作單次只能由一個線程執行。另外,結合隊列先入先出的特性,我們可以實現多個線程操作,按照隊列順序執行,也即優先級。
代碼示例:
import queue
import threading
import time
exitFlag = 0
class myThread (threading.Thread):
def __init__(self, Name, q):
threading.Thread.__init__(self)
self.Name = Name
self.q = q
def run(self):
print ("開啓線程:" + self.Name)
process_data(self.Name, self.q)
print ("退出線程:" + self.Name)
def process_data(Name, q):
while not exitFlag: #線程退出標誌
queueLock.acquire()
if not workQueue.empty(): #隊列非空
data = q.get()
print ("%s processing %s" % (Name, data))
queueLock.release()
time.sleep(1) #爲了演示短暫延遲,防止一個線程全部執行完
threadList = ["Thread-1", "Thread-2", "Thread-3"]
PriorityList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
threads = []
threadID = 1
# 創建新線程
for ThreadName in threadList:
thread = myThread(ThreadName, workQueue) #創建線程
thread.start() #執行線程
threads.append(thread) #添加到線程隊列
threadID += 1
# 填充隊列
for Priority in PriorityList: #主線程進行隊列填充
workQueue.put(Priority) #添加到隊列中
# 等待隊列清空
while not workQueue.empty(): #主線程等待直到隊列爲空
pass
# 通知線程是時候退出
exitFlag = 1 #主線程通知子線程結束
# 等待所有線程完成
for t in threads: #主線程等待所有子線程結束後結束
t.join()
print ("退出主線程")
運行結果:
開啓線程:Thread-1
開啓線程:Thread-2
開啓線程:Thread-3
Thread-2 processing One
Thread-3 processing Two
Thread-1 processing Three
Thread-2 processing Four
Thread-3 processing Five
退出線程:Thread-1
退出線程:Thread-2
退出線程:Thread-3
退出主線程
上面例子通過三個線程去訪問隊列,不管線程誰先執行,最終訪問的順序都是One
到Five
。