您好,登錄后才能下訂單哦!
這篇文章主要介紹“Python進階之多線程怎么實現”,在日常操作中,相信很多人在Python進階之多線程怎么實現問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Python進階之多線程怎么實現”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
想要理解線程的含義,首先我們先看一下百度百科的定義:
線程(英語:thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發多個線程,每條線程并行執行不同的任務。
簡單來講,當你打開電腦中的一個應用程序,其實此時計算機就為你創建了一個進程,系統會為其進行資源分配并且對其進行調度。而線程就是比進程還要小的單位,多個線程完成不同的工作組成了我們宏觀上能夠得到響應的工作結果。
舉個例子,進程就像一個大的工廠,工廠中有很多機床設備和場地。而不同的線程就像工廠中工作的工人,工廠為其分配不同的工作來完成一個最終的生產目標。我們可以指派不同的工人做不同的工作或增加工人提高我們的生產效率。
在編程中,線程可以由我們啟用幫助我們完成不同的工作實現多線程并發,提高我們的代碼效率。
在python中主要有兩種實現多線程的方式:
通過threading.Thread () 方法創建線程
通過繼承 threading.Thread 類的繼承重寫run方法
接下來我們分別說一下多線程的兩種實現形式。
為了更直觀的理解這個過程,首先我們先編寫一個正常的函數,完成倒數5個數的功能,其中間隔一秒鐘。
def fuc(): for i in range(5): time.sleep(1)
在主函數中,我們調用Thread()來實例化兩個線程,讓他們同時運行。
if __name__ == '__main__': t1 = threading.Thread(target=fuc, args=(1,), daemon=True) t2 = threading.Thread(target=fuc, args=(2,), daemon=True) t2.start() t1.start()
整體代碼如下所示:
import threading import time def fuc(): for i in range(5): time.sleep(1) if __name__ == '__main__': t1 = threading.Thread(target=fuc) t2 = threading.Thread(target=fuc) t2.start() t1.start()
我們先不討論調用的函數以及傳入的參數,先來看一下運行效果:
0
0
11
22
33
44
可以看到,兩個打印的結果基本上是同時出現的,并且出現了混合的情況,證明兩個打印的函數正在同時進行。
接下來我們就來介紹一下類的初始化參數以及我們調用的函數:
thread.Thread(group=Nore,targt=None,args=(),kwargs={},*,daemon=None)
在該類中主要由以下幾個參數組成:
group:與ThreadGroup類相關,一般不使用。
target:線程調用的對象,就是目標函數,在上述的例子中我們傳入的是我們編寫的函數fuc。
name:線程的名字,默認是Tread-x。
args:為目標函數傳遞關鍵字參數,字典。
daemon:用來設置線程是否隨主線程退出而退出,涉及到主線程相關知識,我們稍后介紹。
接下來介紹我們常用的幾個方法:
run():表示線程啟動的活動,在第二種繼承寫法中會用到。
start():激活線程,使其能夠被調度。
join():等待至線程終止,這個方法涉及到主線程的知識,我們稍后介紹。
isAlive():返回線程是否活動。
getName():返回線程名稱。
setName() : 設置線程名稱。
接下來,我們使用上述參數更改示例,讓函數獲取一個參數,并為不同的線程設置名字。代碼如下:
import threading import time def fuc(num): for i in range(5): print('接收到參數{}:'.format(num), i) time.sleep(1) if __name__ == '__main__': # 傳入參數及名字 t1 = threading.Thread(target=fuc, args=(1,), name='t1') t2 = threading.Thread(target=fuc, args=(2,), name='t2') t1.start() print(t1.getName(), '開始運行...') t2.start() print(t2.getName(), '開始運行...')
運行結果如下:
接收到參數1:t1 開始運行...
0
接收到參數2: t20 開始運行...
接收到參數1:接收到參數2: 1
1
接收到參數1:接收到參數2: 2
2
接收到參數1:接收到參數2: 33
接收到參數1:接收到參數2: 4
4
可以看到,雖然結果很混亂,但是我們傳入的參數以及獲取的名字都被打印出來了。
另外,這里有兩個注意:
trgat參數接受的是函數名字不需要加括號。
args傳入的執行函數參數要加括號和逗號,保證其是一個元組。
在上面的例子中,我們已經理解了多線程的一種創建方法。接下來我們來介紹第二種方法,這也是眾多大佬很喜歡的一種方法,通過繼承 threading.Thread 類的線程創建。
class MyThread(threading.Thread): def run(self) -> None: for i in range(5): print(i) time.sleep(1) if __name__ == '__main__': t1 = MyThread(name='t1') t2 = MyThread(name='t2') t1.start() t2.start()
運行結果如下:
0
0
11
22
33
44
注意:這里調用的是start方法而不是run方法,否則會編程單線程執行。
在了解了多線程的編程方法之后,我們來介紹一下主線程及相關參數和方法。
在我們執行多線程程序的過程中,存在一個主線程,而我們開辟的其他線程其實都是它的子線程。由主線程主導的工作有以下兩種情況:
由于主線程結束了,強制停止其它線程的工作,但此時其他線程有可能還沒有結束自己的工作。
主線程結束后,等待其他線程結束工作,再停止所有線程的工作。
可以簡單地理解為包工頭,它是這些線程的頭子!其從微觀角度上講掌管了一定的工作流程,它可以選擇是否等待其它工人結束工作再結束整個工作。
而我們可以使用參數或者方法控制這個過程。
在上邊的函數參數介紹中,提到了daemon參數,其為False時,線程不會隨主線程結束而退出,主線程會等待其結束后再退出。而為True時則不論子線程是否完成了相關工作都會直接退出。
接下來我們看兩個示例,我們修改剛才的示例代碼的daemon參數為True,表示不論子線程是否完成了工作都強制退出。
import threading import time def fuc(num): for i in range(5): print('接收到參數{}:'.format(num), i) time.sleep(1) if __name__ == '__main__': t1 = threading.Thread(target=fuc, args=(1,), name='t1', daemon=True) t2 = threading.Thread(target=fuc, args=(2,), name='t2', daemon=True) t1.start() print(t1.getName(), '開始運行...') t2.start() print(t2.getName(), '開始運行...') print("我是主線程,都給我停下!")
結果如下:
接收到參數1:t1 0
開始運行...
接收到參數2:t2 0
開始運行...
我是主線程,都給我停下!
可以看到,子線程的倒數還沒有結束,由于主線程結束了,所有線程一起結束了。 這里要注意以下幾點:
daemon屬性必須在start( )之前設置。
從主線程創建的所有線程不設置daemon屬性,則默認都是daemon=False。
除此之外,我們還可以調用.join()方法阻塞線程,調用該方法的時候,該方法的調用者線程結束后程序才會終止。
#timeout參數表明等待的時長,不設置該參數則默認為一直等待。 join(timeout-=None)
我們來看下面這個示例,我們更改了兩個函數的倒計時時間,使第一個線程的倒計時時間更長,并對第二個線程進行了阻塞操作。代碼如下:
import threading import time def fuc1(): for i in range(10): print(i) time.sleep(1) def fuc2(): for i in range(5): print(i) time.sleep(1) if __name__ == '__main__': t1 = threading.Thread(target=fuc1, name='t1', daemon=True) t2 = threading.Thread(target=fuc2, name='t2', daemon=True) t1.start() print(t1.getName(), '開始運行...') print('我是二兒子,等等我!') t2.start() print(t2.getName(), '開始運行...') t2.join() print("我是主線程,都給我停下!")
結果如下:
0t1
開始運行...
我是二兒子,等等我!
0t2
開始運行...
11
22
33
44
我是主線程,都給我停下!5
我們可以看到,上述代碼中線程一還沒有結束倒數十個數,程序就結束了。在此過程中,主線程只等待了第二個線程結束,整個程序就結束了。
在多個線程同步運行的情況下,會出現多個線程同時操作一個數據的情況。如果兩個線程同時操作同一個變量的話,很容易出現混亂的情況。所以,我們需要一個工具來確保在同一時間只能有一個線程處理數據。
線程類提供了鎖來解決問題,當線程申請處理某個數據時申請一個鎖來控制住當前數據,結束處理時即將鎖釋放。
python的threading中為我們提供了RLock鎖來解決多線程同時處理一個數據的問題。在某個時刻,我們可以讓線程申請鎖來保護數據此時只能供該線程使用。
為了更好的理解該過程,我們定義一個全局變量,讓每一個線程都對其操作但不設置鎖,觀察變量的變化:
R_LOCK = threading.Lock() COUNT = 100 class MyThread(threading.Thread): def run(self) -> None: global COUNT #R_LOCK.acquire() COUNT -= 10 time.sleep(1) print(self.getName(), COUNT) #R_LOCK.release() if __name__ == '__main__': threads = [MyThread() for i in range(10)] for t in threads: t.start()
結果如下:
Thread-3Thread-10 0Thread-8Thread-7 0Thread-6 0Thread-5Thread-9
Thread-1 0Thread-2 00 0
Thread-4 000
可以看到,我們的數據發生了異常,這并不是我們想要得到的結果,若把鎖給關閉注釋讓其正常運行可以看到以下的正常結果:
Thread-1 90
Thread-2 80
Thread-3 70
Thread-4 60
Thread-5 50
Thread-6 40
Thread-7 30
Thread-8 20
Thread-9 10
Thread-10 0
到此,關于“Python進階之多線程怎么實現”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。