微吼云上線多路互動直播服務 加速多場景互動直播落地
781
2025-03-31
我和我的女朋友因為python而相識,同時也是因為python我才能把憨憨追到手。最近我和我女朋友在做一個項目,我負責語音識別和TTS,她負責QT界面設計。終于在上一個周我們都完成了各自預期的功能。到了兩個代碼整合的階段,卻發現了一個難題:怎么樣才能實現語音和界面同時工作,同時怎么樣才能保證通過語音來打開相關的界面,以及在視頻通話時語音不工作,這些問題讓我倆抓狂。看看我女朋友的頭發最近掉的厲害,作為一個男人我必須扛起責任!于是我攔下這活,并且給我女朋友說道:等我學會了python多線程我講給你聽!
文章目錄
線程和進程
多線程與多進程
python多線程的實現
threading
自定義線程
守護線程
主線程等待子線程運行結束
多線程共享全局變量
互斥鎖
遞歸鎖
信號量(BoundedSemaphore類)
事件(Event類)
Qthread
線程和進程
計算機的核心是CPU,它承擔了所有的計算任務,就像是一座工廠在時刻運行
如果工廠的資源有限,一次只能供一個車間來使用,也就是說當一個車間開工時其它車間不能工作,也就是一個CPU一次只能執行一個任務。
進程就好比工廠的車間,它代表CPU所能處理的單個任務。任一時刻,CPU總是運行一個進程,其他進程處于非運行狀態。
當然一個車間還有很多工人,他們互相協同完成一個工作
而線程就好比工廠的工人,一個進程可以包含多個線程
線程(Thread)也叫輕量級進程,是操作系統能夠進行運算調度的最小單位,它被包涵在進程之中,是進程中的實際運作單位。線程自己不擁有系統資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。一個線程可以創建和撤消另一個線程,同一進程中的多個線程之間可以并發執行。
多線程與多進程
通俗易懂的理解就是:
多進程:允許多個任務同時進行 多線程:允許單個任務分成不同的部分運行
1
2
python多線程的實現
Python3 通過兩個標準庫 thread (python2中是thread模塊)和 threading 提供對線程的支持。
thread 提供了低級別的、原始的線程以及一個簡單的鎖,它相比于 threading 模塊的功能還是比較有限的。
threading
import threading #導入threading庫 import time 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",))#創建線程1,取名為t1 t2 = threading.Thread(target=run, args=("t2",))#創建線程2,取名為t2 t1.start() #開啟線程t1 t2.start() #開啟線程t2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
輸出結果:
task t1 task t2 2s 2s 1s 1s 0s 0s
1
2
3
4
5
6
7
8
可以看出先開啟了線程t1,在開啟t2然后每隔一秒打印數據
自定義線程
通過繼承threading.Thread來自定義線程類,其本質是
重構Thread類中的run方法
import threading import time class MyThread(threading.Thread): def __init__(self, n): super(MyThread, self).__init__() # 重構run函數必須要寫 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()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
輸出結果:
task t1 task t2 2s 2s 1s 1s 0s 0s
1
2
3
4
5
6
7
8
守護線程
下面這個例子,使用setDaemon(True)把所有的子線程都變成了主線程的守護線程,因此當主進程結束后,子線程也會隨之結束。所以當主線程結束后,整個程序就退出了。
import threading import time def run(n): print("task", n) time.sleep(1) #此時子線程停1s print('3') time.sleep(1) print('2') time.sleep(1) print('1') if __name__ == '__main__': t = threading.Thread(target=run, args=("t1",)) t.setDaemon(True) #把子進程設置為守護線程,必須在start()之前設置 t.start() print("end")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
輸出結果:
task t1 end
1
2
可以看到,t1線程并沒有執行完畢,而是直接結束了。說明設置子線程為守護線程之后,主線程結束了,子線程也立即結束不再執行。
程序中不是只創建了一個線程么?怎么會有主線程和子線程呢?
其實呢程序運行時就會創建一個線程,而這個線程就是主線程
主線程等待子線程運行結束
import threading import time def run(n): print("task", n) time.sleep(1) print('3') time.sleep(1) print('2') time.sleep(1) print('1') if __name__ == '__main__': t = threading.Thread(target=run, args=("t1",)) t.setDaemon(True) #把子進程設置為守護線程,必須在start()之前設置 t.start() t.join() # 設置主線程等待子線程結束 print("end")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
輸出結果:
task t1 3 2 1 end
1
2
3
4
5
運行.join()后的程序表明等待所有線程結束以后再進行.join()之后的操作結合以上代碼就是,等待t1結束以后再執行end
多線程共享全局變量
線程是進程的執行單元,進程是系統分配資源的最小單位,所以在同一個進程中的多線程是共享資源的。那么共享資源時就需要用到全局變量。
import threading import time num = 100 def work1(): global num for i in range(3): num += 1 print("in work1 num is : %d" % num) def work2(): global num print("in work2 num is : %d" % num) if __name__ == '__main__': t1 = threading.Thread(target=work1) t1.start() time.sleep(1) t2 = threading.Thread(target=work2) t2.start()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
運行結果如下:
in work1 num is : 103 in work2 num is : 103
1
2
可以看到兩者輸出的結果是相同的,說明是可以共享全局變量的。
互斥鎖
由于線程之間是進行隨機調度,并且每個線程可能只執行n條,當多個線程同時修改同一條數據時可能會出現臟數據,因而,出現了線程鎖,即同一時刻只允許一個線程執行操作。線程鎖用于鎖定資源,可以定義多個鎖, 在下面的實例中, 當你需要獨占某一資源時,任何一個鎖都可以鎖這個資源,就好比你用不同的鎖都可以把相同的一個門鎖住是一個道理。
由于線程之間是進行隨機調度,如果有多個線程同時操作一個對象,如果沒有很好地保護該對象,會造成程序結果的不可預期,我們也稱此為“線程不安全”。
為了方式上面情況的發生,就出現了互斥鎖(Lock)
import threading def work1(): global A,lock#定義A和lock為全局變量 lock.acquire()#上鎖 for i in range(5): A+=1 print('work1',A) lock.release()#解鎖 def work2(): global A,lock lock.acquire() for i in range(5): A+=10 print('work2',A) lock.release() if __name__=='__main__': lock=threading.Lock()#定義鎖 A=0 t1=threading.Thread(target=work1) t2=threading.Thread(target=work2) t1.start() t2.start() t1.join() t2.join()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
輸出結果:
work1 1 work1 2 work1 3 work1 4 work1 5 work2 15 work2 25 work2 35 work2 45 work2 55
1
2
3
4
5
6
7
8
9
10
可以發現對兩組數據是沒有影響的,感興趣的可以嘗試一下不加鎖會有什么情況。
遞歸鎖
RLcok類的用法和Lock類一模一樣,但它支持嵌套,在多個鎖沒有釋放的時候一般會使用RLcok類。
import threading import time 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()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
輸出結果:
1 2 3 4 5 6 7 8 9 10
1
2
3
4
5
6
7
8
9
10
信號量(BoundedSemaphore類)
互斥鎖同時只允許一個線程更改數據,而Semaphore是同時允許一定數量的線程更改數據 ,比如廁所有3個坑,那最多只允許3個人上廁所,后面的人只能等里面有人出來了才能再進去。
實際中博主還沒有用到過,所以理解不是特別透徹。
import threading import time def run(n, semaphore): semaphore.acquire() #加鎖 time.sleep(1) 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 # print threading.active_count() else: print('-----all threads done-----')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
輸出結果有點長,就不貼輸出結果了。
事件(Event類)
python線程的事件用于主線程控制其他線程的執行,事件是一個簡單的線程同步對象,其主要提供以下幾個方法:
clear 將flag設置為“False”
set 將flag設置為“True”
is_set 判斷是否設置了flag
wait 會一直監聽flag,如果沒有檢測到flag就一直處于阻塞狀態
事件處理的機制:全局定義了一個“Flag”,當flag值為“False”,那么event.wait()就會阻塞,當flag值為“True”,那么event.wait()便不再阻塞
import threading import time event = threading.Event() def lighter(): count = 0 event.set() #初始值為綠燈 while True: if 5 < count <=10 : event.clear() # 紅燈,清除標志位 print("1mred light is on...") elif count > 10: event.set() # 綠燈,設置標志位 count = 0 else: print("mgreen light is on...") 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) light = threading.Thread(target=lighter,) light.start() car = threading.Thread(target=car,args=("MINI",)) car.start()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
這段代碼模擬紅綠燈,很形象。
Qthread
本以為我學完了多線程就完事了,就可以將語音和QT界面進行整合了。當我去實現的時候發現問題不是這么簡單,通過語音控制打開一個特定的界面可以實現,但是為什么只要這個特定的界面關閉了,我語音的線程也就結束了。
困惑了我好久,最后終于在某社區發現了答案!原來QT自帶的有Qthread,當多線程涉及到界面交互時最好用Qthread來實現。然后又查閱大量博客,看了大量代碼
在使用繼承QThread的run方法之前需要了解一條規則:
QThread只有run函數是在新線程里的,其他所有函數都在QThread生成的線程里
QThread只有run函數是在新線程里的
QThread只有run函數是在新線程里的
QThread只有run函數是在新線程里的
那么我就在網上找到了這個計時器的例子:
#coding=utf-8 import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.QtCore import * count = 0 # 工作線程 class WorkThread(QThread): # pyqtSignal是信號類 timeout = pyqtSignal() # 每隔一秒發送一個信號 end = pyqtSignal() # 計數完成后發送一個信號 def run(self): while True: # 休眠1秒 self.sleep(1) if count == 5: self.end.emit() # 發送end信號,調用和end信號關聯的方法 break self.timeout.emit() # 發送timeout信號 class Counter(QWidget): def __init__(self): super(Counter, self).__init__() self.setWindowTitle("用QThread編寫計數器") self.resize(600, 400) layout = QVBoxLayout() # QLCDNumber 用于模擬LED顯示效果,類似于Label self.lcdNumber = QLCDNumber() layout.addWidget(self.lcdNumber) button = QPushButton("開始計數") layout.addWidget(button) self.workThread = WorkThread() self.workThread.timeout.connect(self.countTime) self.workThread.end.connect(self.end) button.clicked.connect(self.work) self.setLayout(layout) def countTime(self): global count count += 1 self.lcdNumber.display(count) def end(self): QMessageBox.information(self, '消息', '計數結束', QMessageBox.Ok) global count count =0 def work(self): self.workThread.start() if __name__ == "__main__": app = QApplication(sys.argv) main = Counter() main.show() sys.exit(app.exec_())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
點擊開始計時就會出現類似LCD的顯示,計時到5秒結束后彈窗提醒。
運行結果如下:
通過這個例程讓我對Qthread有了更好的理解,經管理解的不是特別透徹但是我知道怎么來改出來我想用的代碼。之前提到的打開窗口線程阻塞,關閉窗口線程重啟,其實這個計時器是一個很好的例子,但是關于線程阻塞.wait不好使。我的方法是定義一個全局變量mode=0(用來判斷是否需要阻塞線程),如果窗口打開后那么給這個全局賦值mode=1,在run函數里對這個mode進行判斷,如果mode等于1那么可以用一個循環來延時實現。
if mode: while(mode): self.sleep(1)
1
2
3
當窗口關閉以后給mode 賦值等于0通過這種方法可以實現,很多小伙伴又會問怎么判斷窗口打開和關閉,其實在自己寫的窗口函數最前面加mode=1和最后面mode=0就可以了不用進行判斷。
在看完這篇文章后,我女朋友終于給我發來了下面的表情,對我投來羨慕的眼神
Python 任務調度 多線程
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。