-
區分程式和執行緒
執行緒和程式是作業系統相關的概念,比如在電腦上可以一邊聽歌,一邊瀏覽網頁,當你開啟一個音樂播放器的時候就會啟動一個程式,開啟瀏覽器的時候也會啟動一個程式。
在音樂播放器中我們在聽歌的同時,發表一些評論或者搜尋其他歌曲。在程式裡同時執行的這些任務,叫做執行緒。一個程式裡是可以包含多個執行緒的。
-
Python多執行緒程式設計
Python中多執行緒程式設計需要藉助的模組是:threading
-
單執行緒和多執行緒對比:
-
單執行緒:
import time # 單執行緒 def sayHello(index): print('hello!', index) time.sleep(1) print('執行完成') for i in range(5): sayHello(i) 複製程式碼
結果:
例子中看單執行緒程式執行呢就是一個接著一個執行 -
多執行緒:
import time import threading def sayHello(index): print('hello!', index) time.sleep(1) print('執行完成') for i in range(5): t1 = threading.Thread(target=sayHello, args=(i,)) t1.start() 複製程式碼
結果:
多執行緒則像是超市的收銀臺一樣,2號收銀臺不用等1號收銀臺的顧客全部交完錢了再開始工作,可以一起工作。
-
-
-
threading.Thread
threading.Thread函式,建立一個執行緒。
使用help函式可以看到它接受的引數有這些:
Thread(group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None) |*group* should be None; reserved for future extension when a ThreadGroup |class is implemented. | |*target* is the callable object to be invoked by the run() |method. Defaults to None, meaning nothing is called. | |*name* is the thread name. By default, a unique name is constructed of |the form "Thread-N" where N is a small decimal number. | |*args* is the argument tuple for the target invocation. Defaults to (). | |*kwargs* is a dictionary of keyword arguments for the target |invocation. Defaults to {}. 複製程式碼
target就是執行緒啟動後執行的函式
args和kwargs都是這個函式的引數,args是元組型別,kwargs是關鍵字型別引數。
name是給這個執行緒的名字,如果不傳就是Thread-1, Thread-2....
它還有一些方法:
- run(): 執行執行緒中的方法。
- start(): 啟動執行緒。
- join(): 等待執行緒結束。
- isAlive(): 返回執行緒是否活動的。
- getName(): 返回執行緒名。
- setName(): 設定執行緒名。
-
threading.currentThread
threading.currentThread可以獲得當前的執行緒資訊,例子:
import time from threading import Thread, currentThread def sayHello(index): print(currentThread().getName(), index) time.sleep(1) print('執行完成') for i in range(5): t1 = Thread(target=sayHello, kwargs={'index': 1}) t1.start() 複製程式碼
結果:
結合Thread類的join方法,可以看下主執行緒和子執行緒之間的順序:
from threading import Thread, currentThread
import time
def do_somethong():
print(currentThread().getName(), '開始')
time.sleep(1)
print(currentThread().getName(), '結束')
t1 = Thread(target=do_somethong)
t1.start()
print(currentThread().getName(), '結束')
複製程式碼
結果:
MainThread就是主執行緒,可以看到是主執行緒先結束Thread-1執行緒再結束的,如果想要主執行緒最後結束,那麼就可以使用join方法了。
from threading import Thread, currentThread
import time
def do_somethong():
print(currentThread().getName(), '開始')
time.sleep(1)
print(currentThread().getName(), '結束')
t1 = Thread(target=do_somethong)
t1.start()
t1.join() # 新增
print(currentThread().getName(), '結束')
複製程式碼
-
多個執行緒之間的變數
from threading import Thread
a = 0
def change_num(num):
global a
a += 1
a -= 1
def run_thread(num):
for i in range(100000):
change_num(num)
t1 = Thread(target=run_thread, args=(1,))
t2 = Thread(target=run_thread, args=(2,))
t1.start()
t2.start()
t1.join()
t2.join()
print(a)
複製程式碼
多次執行上面程式碼的結果:
a的值應該永遠是0的,但是有時候會變,多執行緒中的變數是共享的,如果同時去改一個變數,那麼有可能會將該變數改亂。原因
如果想要多個執行緒修改同一變數,那麼就需要一個鎖來確保該變數修改後的正確性。 鎖就是當一個執行緒在修改的時候,其他執行緒等待。這個執行緒修改完了,下個執行緒繼續修改。
實現這個鎖,要用到threading.lock()方法:
from threading import Thread, Lock
a = 0
thread_lock = Lock() # 建立一個鎖
def change_num(num):
global a
a += 1
a -= 1
def run_thread(num):
for i in range(100000):
try:
thread_lock.acquire() # 鎖住
change_num(num)
finally:
thread_lock.release() # 解鎖
t1 = Thread(target=run_thread, args=(1,))
t2 = Thread(target=run_thread, args=(2,))
t1.start()
t2.start()
t1.join()
t2.join()
print(a)
複製程式碼
-
threading.local
執行緒之間如果使用區域性變數,那麼就可以用threading.local這個方法。例子:
from threading import Thread, currentThread, local
data = local()
def do_somethong(name):
data.name = name
print(currentThread().getName(), data.name)
t1 = Thread(target=do_somethong, args=('jack',))
t2 = Thread(target=do_somethong, args=('alex',))
t1.start()
t2.start()
t1.join()
t2.join()
複製程式碼
結果:
用threading.local的好處是,當執行緒中函式比較複雜的時候,也就是說不僅僅只是執行一個函式,而是多個函式,比如:
from threading import Thread
import time
def sayHelle(name):
print('hello!%s' % name)
def sayBye(name):
print('goodbye!%s' % name)
def run(name):
sayHelle(name)
time.sleep(1)
sayBye(name)
t1 = Thread(target=run, args=('jack',))
t1 = Thread(target=run, args=('jack',))
t1.start()
t2.start()
t1.join()
t2.join()
複製程式碼
這個name引數就得一個一個的傳遞下去,這樣很不方便,threading.lock()方法就像是全域性的一個字典,每一個執行緒從裡面獲取name的時候都是各自執行緒自己存進去的name,相互不會干擾。