通過使用threading模塊能完成多任務的程序開發,但 實際開發中爲了讓每個線程的封裝性更完美,所以使用threading模塊時,往往會定義一個新的子類class,只要繼承threading.Thread
就可以了,然後重寫父類的run
方法即可
1.自定義線程類
import time
class Test_Thread(threading.Thread): #1.自定義線程類,繼承threading.Thread
def run(self): #2.重寫父類的run方法
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' @ '+str(i) #name屬性中保存的是當前線程的名字
print(msg)
if __name__ == '__main__':
t = Test_Thread() #創建線程對象
t.start() #這裏的start方法是父類Threading.Thread裏面的方法,start()會調用run方法(重寫的run)
'''
I'm Thread-1 @ 0
I'm Thread-1 @ 1
I'm Thread-1 @ 2
'''
python的threading.Thread類有一個run方法,用於定義線程的功能函數,可以在自己的線程類中覆蓋該方法。而創建自己的線程實例後,通過Thread類的start方法,可以啓動該線程,交給python虛擬機進行調度,當該線程獲得執行的機會時,就會調用run方法執行線程。
2.注意線程的執行順序
多線程執行時,執行順序是由cpu進行調度,所以下面程序每次執行 的順序幾乎都不一致。
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' @ '+str(i) #self.name獲取線程的名稱
print(msg)
def test():
for i in range(5):
t = MyThread()
t.start()
if __name__ == '__main__':
test()
''' 可以看出5個線程同時執行,同時執行5個run裏面的循環,但每次循環執行的時間大體一致
I'm Thread-3 @ 0
I'm Thread-1 @ 0
I'm Thread-2 @ 0
I'm Thread-5 @ 0
I'm Thread-4 @ 0
I'm Thread-1 @ 1
I'm Thread-3 @ 1
I'm Thread-2 @ 1
I'm Thread-5 @ 1
I'm Thread-4 @ 1
I'm Thread-3 @ 2
I'm Thread-2 @ 2
I'm Thread-1 @ 2
I'm Thread-5 @ 2
I'm Thread-4 @ 2
'''
從代碼和執行結果我們可以看出,多線程程序的執行順序是不確定的。當執行到sleep語句時,線程將被阻塞(Blocked),到sleep結束後,線程進入就緒(Runnable)狀態,等待調度。而線程調度將自行選擇一個線程執行。上面的代碼中只能保證每個線程都運行完整個run函數,但是線程的啓動順序、run函數中每次循環的執行順序都不能確定。
總結
- 每個線程默認有一個名字,儘管上面的例子中沒有指定線程對象的name,但是python會自動爲線程指定一個名字。
- 當線程的run()方法結束時該線程完成。
- 無法控制線程調度程序,但可以通過別的方式來影響線程調度的方式。
3.python中線程共享全局變量的問題
3.1實現線程之間共享全局變量的方式
from threading import Thread
import time
num = 100
ls1 = [9,8,7]
def work1():
global num
for i in range(3):
num += 1
ls1.append(i)
print("----in work1, num is %d---"%num)
def work2():
global num # 注意這裏num是全局不可變對象變量,所以在函數裏修改它需要聲明,否則報錯。
print("----in work2, num is %d---"%num)
print("----in work2, ls1 is :"+str(ls1)) #注意這裏ls1是全局可變對象變量,所以在函數裏修改它不用聲明
'''1.在一個函數中對全局變量進行修改時,如果是不可變對象包括int,float,string,tuple等,則需要對該全局變量用global聲明
2.如果該全局變量是可變對象包括list,dict,自定義類的實例等,則不需要用global進行聲明。
'''
print("---線程創建之前num is %d---"%num)
print("---線程創建之前num is %d---"+str(ls1))
t1 = Thread(target=work1)
t1.start()
time.sleep(2) #延時一會,保證t1線程中的事情做完
t2 = Thread(target=work2)
t2.start()
'''
---線程創建之前num is 100---
---線程創建之前num is %d---[9, 8, 7]
----in work1, num is 103---
----in work2, num is 103---
----in work2, ls1 is :[9, 8, 7, 0, 1, 2]
'''
要點總結:
- 在一個函數中對全局變量進行修改時,如果是不可變對象包括int,float,string,tuple等,則需要對該全局變量用global聲明
- 如果該全局變量是可變對象包括list,dict,自定義類的實例等,則不需要用global進行聲明。
- 在一個進程內的所有線程共享全局變量,很方便在多個線程間共享數據
- 缺點就是,線程是對全局變量隨意遂改可能造成多線程之間對全局變量的混亂(即線程非安全)
3.2多線程共享去全局變量存在的風險
import threading
import time
g_num = 0
def work1(num):
global g_num
for i in range(num):
g_num += 1
print("----in work1, g_num is %d---"%g_num)
def work2(num):
global g_num
for i in range(num):
g_num += 1
print("----in work2, g_num is %d---"%g_num)
print("---線程創建之前g_num is %d---"%g_num)
t1 = threading.Thread(target=work1, args=(1000000,))
#給線程執行的函數傳參,注意這裏args只是變量名,可以自己定義。
t1.start()
t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()
while len(threading.enumerate()) != 1:
time.sleep(1)
print("2個線程對同一個全局變量操作之後的最終結果是:%s" % g_num)
'''注意,如果參數很小的話,數據可能準備,但是參數很大的時候就會出錯。
---線程創建之前g_num is 0---
----in work1, g_num is 1279887---
----in work2, g_num is 1425013---
2個線程對同一個全局變量操作之後的最終結果是:1425013
'''
- 如果多個線程同時對同一個全局變量操作,會出現資源競爭問題,從而數據結果會不正確。所以對於多線程共享全局變量存在一定的風險。當然後續有相應的解決方案,如鎖的實現。