python基礎執行緒-管理併發執行緒

Python_sn發表於2020-09-27

執行緒模組建立線上程的底層特性之上,使執行緒的工作變得更簡單、更像python。使用執行緒允許程式在同一程式空間中併發執行多個操作。

很多人學習python,不知道從何學起。
很多人學習python,掌握了基本語法過後,不知道在哪裡尋找案例上手。
很多已經做案例的人,卻不知道如何去學習更加高深的知識。
那麼針對這三類人,我給大家提供一個好的學習平臺,免費領取視訊教程,電子書籍,以及課程的原始碼!
QQ群:961562169

執行緒物件

使用執行緒最簡單的方法是用目標函式例項化它,然後呼叫start()讓它開始工作。

import threading

def worker():
    """執行緒worker函式"""
    print('Worker')
    return

threads = []
for i in range(5):
    t = threading.Thread(target=worker)
    threads.append(t)
    t.start()

結果:輸出為五行,每行上都有“Worker”

$ python3 threading_simple.py

Worker
Worker
Worker
Worker
Worker

能夠生成一個執行緒並傳遞引數來告訴它要做什麼工作是很有用的。這個例子傳遞一個數字,然後執行緒列印這個數字。

import threading

def worker(num):
    """執行緒worker函式"""
    print('Worker: {num}' )
    return

threads = []
for i in range(5):
    t = threading.Thread(target=worker, args=(i,))
    threads.append(t)
    t.start()

現在每個執行緒列印的訊息包含一個數字:

$ python3 -u threading_simpleargs.py

Worker: 0
Worker: 1
Worker: 2
Worker: 3
Worker: 4

確定當前執行緒

使用引數來標識或命名執行緒很麻煩,而且沒有必要。每個Thread例項都有一個具有預設值的名稱,該名字可以在建立執行緒時更改。命名執行緒在具有多個服務執行緒來處理不同操作的伺服器程式中很有用。

import threading
import time

def worker():
    print(threading.currentThread().getName(), '開始執行')
    time.sleep(2)
    print(threading.currentThread().getName(), '結束執行')

def my_service():
    print(threading.currentThread().getName(), '開始執行')
    time.sleep(3)
    print(threading.currentThread().getName(), '結束執行')

t = threading.Thread(name='my_service', target=my_service)
w = threading.Thread(name='worker', target=worker)
w2 = threading.Thread(target=worker) # 使用預設名字

w.start()
w2.start()
t.start()

除錯輸出包括每行上當前執行緒的名稱。“執行緒名稱”列中帶有“Thread-1”的行對應於未命名的執行緒w2。

$ python -u threading_names.py

worker Thread-1 開始執行
my_service 開始執行
開始執行
Thread-1worker 結束執行
結束執行
my_service 結束執行

大多數程式不使用列印進行除錯。logging支援使用格式化程式程式碼%(threadName)在每個日誌訊息中嵌入執行緒名稱。在日誌訊息中包含執行緒名稱可以更容易地將這些訊息追溯到其源。

import logging
import threading
import time

logging.basicConfig(level=logging.DEBUG,
                    format='[%(levelname)s] (%(threadName)-10s) %(message)s',
                    )


def worker():
    logging.debug('開始執行')
    time.sleep(2)
    logging.debug('結束執行')


def my_service():
    logging.debug('開始執行')
    time.sleep(3)
    logging.debug('結束執行')


t = threading.Thread(name='my_service', target=my_service)
w = threading.Thread(name='worker', target=worker)
w2 = threading.Thread(target=worker)  # 使用預設名字

w.start()
w2.start()
t.start()

logging是執行緒安全的,因此來自不同執行緒的訊息在輸出中保持不同。

$ python threading_names_log.py

[DEBUG] (worker    ) 開始執行
[DEBUG] (Thread-1  ) 開始執行
[DEBUG] (my_service) 開始執行
[DEBUG] (worker    ) 結束執行
[DEBUG] (Thread-1  ) 結束執行
[DEBUG] (my_service) 結束執行

守護程式與非守護程式執行緒

到目前為止,示例程式已隱式地等待退出,直到所有執行緒都完成了它們的工作。有時程式生成一個執行緒作為守護程式執行,而該執行緒在執行時不會阻止主程式退出。使用守護程式執行緒對於那些可能無法輕鬆中斷執行緒或讓執行緒在其工作過程中死亡不會丟失或損壞資料的服務(例如,為服務監視工具生成“心跳”的執行緒)非常有用。要將執行緒標記為守護程式,請使用布林引數呼叫其setDaemon()方法。預設情況下,執行緒不是守護程式,因此傳遞True將開啟守護程式模式。

import threading
import time
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-10s) %(message)s',
                    )


def daemon():
    logging.debug('開始執行')
    time.sleep(2)
    logging.debug('結束執行')


d = threading.Thread(name='daemon', target=daemon)
d.setDaemon(True)


def non_daemon():
    logging.debug('開始執行')
    logging.debug('結束執行')


t = threading.Thread(name='non-daemon', target=non_daemon)

d.start()
t.start()

請注意,輸出不包括來自守護程式執行緒的“結束執行”訊息,因為所有非守護程式執行緒(包括主執行緒)都在守護程式執行緒從其兩秒鐘的睡眠中喚醒之前退出。

$ python threading_daemon.py

(daemon    ) 開始執行
(non-daemon) 開始執行
(non-daemon) 結束執行

要等到守護程式執行緒完成其工作,請使用join()方法。

import threading
import time
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-10s) %(message)s',
                    )

def daemon():
    logging.debug('開始執行')
    time.sleep(2)
    logging.debug('結束執行')

d = threading.Thread(name='daemon', target=daemon)
d.setDaemon(True)

def non_daemon():
    logging.debug('開始執行')
    logging.debug('結束執行')

t = threading.Thread(name='non-daemon', target=non_daemon)

d.start()
t.start()

d.join()
t.join()

等待守護執行緒使用join()退出意味著它有機會生成“結束執行”訊息。

$ python threading_daemon_join.py

(daemon    ) 開始執行
(non-daemon) 開始執行
(non-daemon) 結束執行
(daemon    ) 結束執行

預設情況下,join()無限期阻塞。也可以傳遞一個超時引數(一個浮點數,表示等待執行緒變為非活動狀態的秒數)。如果執行緒沒有在超時時間內完成,join()仍然返回。

import threading
import time
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-10s) %(message)s',
                    )

def daemon():
    logging.debug('開始執行')
    time.sleep(2)
    logging.debug('結束執行')

d = threading.Thread(name='daemon', target=daemon)
d.setDaemon(True)

def non_daemon():
    logging.debug('開始執行')
    logging.debug('結束執行')

t = threading.Thread(name='non-daemon', target=non_daemon)

d.start()
t.start()

d.join(1)
# python2寫法
print ('d.isAlive()', d.isAlive())
# python3寫法
print ('d.is_alive()', d.is_alive())
t.join()

由於傳遞的超時值小於守護程式執行緒的睡眠時間,因此join()返回後,執行緒仍然是“活動的”。

$ python threading_daemon_join_timeout.py

(daemon    ) 開始執行
(non-daemon) 開始執行
(non-daemon) 結束執行
d.isAlive() True
d.is_alive() True

列舉所有執行緒

沒有必要保留所有守護程式執行緒的顯式控制程式碼,以確保它們在退出主程式之前已完成。enumerate()返回活動執行緒例項的列表。該列表包含當前執行緒,並且由於不允許加入當前執行緒(這會導致死鎖情況),因此必須跳過它。

import random
import threading
import time
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-10s) %(message)s',
                    )

def worker():
    """執行緒worker函式"""
    t = threading.currentThread()
    pause = random.randint(1,5)
    logging.debug('sleeping %s', pause)
    time.sleep(pause)
    logging.debug('ending')
    return

for i in range(3):
    t = threading.Thread(target=worker)
    t.setDaemon(True)
    t.start()

main_thread = threading.currentThread()
for t in threading.enumerate():
    if t is main_thread:
        continue
    logging.debug(f'joining { t.getName()}')
    t.join()

由於worker睡眠的時間是隨機的,所以這個程式的輸出可能會有所不同。應該是這樣的:

$ python threading_enumerate.py

(Thread-1  ) sleeping 3
(Thread-2  ) sleeping 2
(Thread-3  ) sleeping 5
(MainThread) joining Thread-1
(Thread-2  ) ending
(Thread-1  ) ending
(MainThread) joining Thread-3
(Thread-3  ) ending
(MainThread) joining Thread-

子類化執行緒

在啟動時,執行緒進行一些基本的初始化,然後呼叫其run()方法,該方法呼叫傳遞給建構函式的目標函式。若要建立Thread的子類,請重寫run()以執行任何必要的操作。

import threading
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-10s) %(message)s',
                    )

class MyThread(threading.Thread):

    def run(self):
        logging.debug('running')
        return

for i in range(5):
    t = MyThread()
    t.start()

忽略run()的返回值。

$ python threading_subclass.py

(Thread-1  ) running
(Thread-2  ) running
(Thread-3  ) running
(Thread-4  ) running
(Thread-5  ) running

因為傳遞給Thread建構函式的args和kwargs值儲存在私有變數中,因此不容易從子類訪問它們。要將引數傳遞給自定義執行緒型別,請重新定義建構函式以將值儲存在例項屬性中,該屬性可以在子類中看到。

import threading
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-10s) %(message)s',
                    )

class MyThreadWithArgs(threading.Thread):

    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs=None, verbose=None):
        threading.Thread.__init__(self, group=group, target=target, name=name,
                                  verbose=verbose)
        self.args = args
        self.kwargs = kwargs
        return

    def run(self):
        logging.debug('running with %s and %s', self.args, self.kwargs)
        return

for i in range(5):
    t = MyThreadWithArgs(args=(i,), kwargs={'a':'A', 'b':'B'})
    t.start()

MyThreadWithArgs與Thread使用相同的API,但是與其他任何類一樣,另一個類可以輕鬆地更改建構函式方法以採用與執行緒目的更直接相關的更多或不同的引數。

$ python threading_subclass_args.py

(Thread-1  ) running with (0,) and {'a': 'A', 'b': 'B'}
(Thread-2  ) running with (1,) and {'a': 'A', 'b': 'B'}
(Thread-3  ) running with (2,) and {'a': 'A', 'b': 'B'}
(Thread-4  ) running with (3,) and {'a': 'A', 'b': 'B'}
(Thread-5  ) running with (4,) and {'a': 'A', 'b': 'B'}

計時器執行緒

Timer提供了一個將Thread子類化的原因的示例,它也包含線上程中。計時器在延遲之後開始工作,並且可以在該延遲時間段內的任何時間取消。

import threading
import time
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-10s) %(message)s',
                    )

def delayed():
    logging.debug('worker running')
    return

t1 = threading.Timer(3, delayed)
t1.setName('t1')
t2 = threading.Timer(3, delayed)
t2.setName('t2')

logging.debug('starting timers')
t1.start()
t2.start()

logging.debug('waiting before canceling %s', t2.getName())
time.sleep(2)
logging.debug('canceling %s', t2.getName())
t2.cancel()
logging.debug('done')

請注意,第二個計時器從不執行,而第一個計時器似乎在主程式的其餘部分完成後執行。因為它不是守護程式執行緒,所以當主執行緒完成時,它是隱式連線的。

$ python threading_timer.py

(MainThread) starting timers
(MainThread) waiting before canceling t2
(MainThread) canceling t2
(MainThread) done
(t1        ) worker running

執行緒間信令

雖然使用多個執行緒的目的是分離出單獨的操作以併發執行,但有時能夠在兩個或多個執行緒中同步這些操作是很重要的。執行緒之間通訊的一個簡單方法是使用事件物件。事件管理內部標誌,呼叫者可以set()或clear()。其他執行緒可以wait()設定set(),有效地阻止程式,直到允許繼續。

import logging
import threading
import time

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-10s) %(message)s',
                    )
                    
def wait_for_event(e):
    """Wait for the event to be set before doing anything"""
    logging.debug('wait_for_event starting')
    event_is_set = e.wait()
    logging.debug('event set: %s', event_is_set)

def wait_for_event_timeout(e, t):
    """Wait t seconds and then timeout"""
    while not e.isSet():
        logging.debug('wait_for_event_timeout starting')
        event_is_set = e.wait(t)
        logging.debug('event set: %s', event_is_set)
        if event_is_set:
            logging.debug('processing event')
        else:
            logging.debug('doing other work')


e = threading.Event()
t1 = threading.Thread(name='block', 
                      target=wait_for_event,
                      args=(e,))
t1.start()

t2 = threading.Thread(name='non-block', 
                      target=wait_for_event_timeout, 
                      args=(e, 2))
t2.start()

logging.debug('Waiting before calling Event.set()')
time.sleep(3)
e.set()
logging.debug('Event is set')

wait()方法在等待表示時間的引數之前佔用了秒數。它返回一個布林值,指示是否設定了事件,因此呼叫者知道wait()返回的原因。isSet()方法可以單獨用於事件,而不必擔心阻塞。

在本例中,wait_for_event_timeout()檢查事件狀態,不會無限期阻塞。wait_for_event()會阻塞對wait()的呼叫,直到事件狀態更改後才會返回。

$ python threading_event.py

(block     ) wait_for_event starting
(non-block ) wait_for_event_timeout starting
(MainThread) Waiting before calling Event.set()
(non-block ) event set: False
(non-block ) doing other work
(non-block ) wait_for_event_timeout starting
(MainThread) Event is set
(block     ) event set: True
(non-block ) event set: True
(non-block ) processing event

相關文章