threading 模塊支持守護線程, 其工作方式是:守護線程一般是一個等待客戶端請求服務的服務器。
如果把一個線程設置為守護線程,進程退出時不需要等待這個線程執行完成。
如果主線程准備退出時,不需要等待某些子線程完成,就可以為這些子線程設置守護線程標記。 需要在啟動線程之前執行如下賦值語句: thread.daemon = True,檢查線程的守護狀態也只需要檢查這個值即可。
整個 Python 程序將在所有非守護線程退出之后才退出, 換句話說, 就是沒有剩下存活的非守護線程時才退出。
使用thread模塊
以下是三種使用 Thread 類的方法(一般使用第一個或第三個方案)
-
創建 Thread 的實例,傳給它一個函數。
import threading
from time import sleep, ctime
loops = [3, 2, 1, 1, 1]
def loop(i, nsec):
print(f'start loop {i} at: {ctime()}')
sleep(nsec)
print(f'end loop {i} at: {ctime()}')
def main():
print('start at', ctime())
threads = []
nloops = range(len(loops))
for i in nloops:
t = threading.Thread(target=loop, args=(i, loops[i]))
threads.append(t)
for i in nloops: # start threads
threads[i].start()
for i in nloops: # wait for all
threads[i].join() # threads to finish
print(f'all done at: {ctime()}')
if __name__ == '__main__':
main()
當所有線程都分配完成之后,通過調用每個線程的 start()方法讓它們開始執行,而不是 在這之前就會執行。
相比於管理一組鎖(分配、獲取、釋放、檢查鎖狀態等)而言,這里只 需要為每個線程調用 join()方法即可。
join()方法將等待線程結束,或者在提供了超時時間的情況下,達到超時時間。
使用 join()方法要比等待鎖釋放的無限循環更加清晰(這也是這種鎖 又稱為自旋鎖的原因)。
-
創建 Thread 的實例,傳給它一個可調用的類實例。
import threading
from time import sleep, ctime
# 創建 Thread 的實例,傳給它一個可調用的類實例
loops = [3, 2, 1, 1, 1]
class ThreadFunc(object):
def __init__(self, func, args, name=''):
self.name = name
self.func = func
self.args = args
def __call__(self):
self.func(*self.args)
def loop(i, nsec):
print(f'start loop {i} at: {ctime()}')
sleep(nsec)
print(f'end loop {i} at: {ctime()}')
def main():
print('start at', ctime())
threads = []
nloops = range(len(loops))
for i in nloops:
t = threading.Thread(target=ThreadFunc(loop, (i, loops[i]), loop.__name__))
threads.append(t)
for i in nloops: # start threads
threads[i].start()
for i in nloops: # wait for all
threads[i].join() # threads to finish
print(f'all done at: {ctime()}')
if __name__ == '__main__':
main()
-
派生 Thread 的子類,並創建子類的實例。
import threading
from time import sleep, ctime
# 創建 Thread 的實例,傳給它一個可調用的類實例
# 子類的構造函數必須先調用其基類的構造函數
# 特殊方法__call__()在 子類中必須要寫為 run()
loops = [3, 2, 1, 1, 1]
class MyThread(threading.Thread):
def __init__(self, func, args, name=''):
threading.Thread.__init__(self)
self.name = name
self.func = func
self.args = args
def run(self):
self.func(*self.args)
def loop(i, nsec):
print(f'start loop {i} at: {ctime()}')
sleep(nsec)
print(f'end loop {i} at: {ctime()}')
def main():
print('start at', ctime())
threads = []
nloops = range(len(loops))
for i in nloops:
t = MyThread(loop, (i, loops[i]), loop.__name__)
threads.append(t)
for i in nloops: # start threads
threads[i].start()
for i in nloops: # wait for all
threads[i].join() # threads to finish
print(f'all done at: {ctime()}')
if __name__ == '__main__':
main()
使用鎖
python和java一樣,也具有鎖機制,而且創建與使用鎖都是很簡便的。
一般在多線程代碼中,總會有一些特 定的函數或代碼塊不希望(或不應該)被多個線程同時執行,通常包括修改數據庫、更新文件或 其他會產生競態條件的類似情況
鎖有兩種狀態:鎖定和未鎖定。而且它也只支持兩個函數:獲得鎖和釋放鎖。
一般鎖的調用如下
# 加載線程的鎖對象 lock = threading.Lock() # 獲取鎖 lock.acquire() # ...代碼 # 釋放鎖 lock.release()
更簡潔的方法是使用with關鍵字,如下代碼功能同上
# 加載線程的鎖對象
lock = threading.Lock()
with lock :
#...代碼
示例代碼:
import threading
from time import sleep, ctime
lock = threading.Lock()
def a():
lock.acquire()
for x in range(5):
print(f'a:{str(x)}')
sleep(0.01)
lock.release()
def b():
lock.acquire()
for x in range(5):
print(f'a:{str(x)}')
sleep(0.01)
lock.release()
threading.Thread(target=a).start()
threading.Thread(target=b).start()
相關屬性和方法
-
Thread對象的屬性
| 屬性 | 描述 |
|---|---|
| name | 線程名 |
| ident | 線程的標識符 |
| daemon | 布爾標志,表示這個線程是否是守護線程 |
-
Thread對象的方法
| 方法 | 描述 |
|---|---|
| init(group=None, tatget=None, name=None, args=(), kwargs ={}, verbose=None, daemon=None) | 實例化一個線程對象,需要有一個可調用的 target,以及其參數 args 或 kwargs。還可以傳遞 name 或 group 參數,不過后者還未實現。此 外 , verbose 標 志 也 是 可 接 受 的 。 而 daemon 的 值 將 會 設 定 thread.daemon 屬性/標志 |
| start() | 開始執行該線程 |
| run() | 定義線程功能的方法(通常在子類中被應用開發者重寫) |
| join (timeout=None) | 直至啟動的線程終止之前一直掛起;除非給出了 timeout(秒),否則 會一直阻塞 |
| is_alive() | 布爾標志,表示這個線程是否還存活 |
-
threading模塊其他函數
| 函數 | 描述 |
|---|---|
| start() | 開始執行該線程 |
| active_count() | 當前活動的 Thread 對象個數 |
| enumerate() | 返回當前活動的 Thread 對象列表 |
| settrace(func) | 為所有線程設置一個 trace 函數 |
| setprofile (func) | 為所有線程設置一個 profile 函數 |
| stack_size(size=0) | 返回新創建線程的棧大小;或為后續創建的線程設定棧的大小 為 size |
| Lock() | 加載線程的鎖對象,是一個基本的鎖對象,一次只能一個鎖定,其余鎖請求,需等待鎖釋放后才能獲取,對象有acquire()和release()方法 |
| RLock() | 多重鎖,在同一線程中可用被多次acquire。如果使用RLock,那么acquire和release必須成對出現,調用了n次acquire鎖請求,則必須調用n次的release才能在線程中釋放鎖對象 |
后記
在Python多線程下,每個線程的執行方式:
1、獲取GIL
2、執行代碼直到sleep或者是python虛擬機將其掛起。
3、釋放GIL
通常來說,多線程是一個好東西。不過由於 Python 的 GIL 的限制,多線程更適合於 I/O 密集型應用(I/O 釋放了 GIL,可以允 許更多的並發),而不是計算密集型應用。對於后一種情況而言,為了實現更好的並行性,你需要使用多進程,以便讓 CPU 的其他內核來執行。
請注意:多核多線程比單核多線程更差,原因是單核下多線程,每次釋放GIL,喚醒的那個線程都能獲取到GIL鎖,所以能夠無縫執行,但多核下,CPU0釋放GIL后,其他CPU上的線程都會進行競爭,但GIL可能會馬上又被CPU0拿到,導致其他幾個CPU上被喚醒后的線程會醒着等待到切換時間后又進入待調度狀態,這樣會造成線程顛簸(thrashing),導致效率更低
