python自定義線程類的使用與共享全局變量的問題

       通過使用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函數中每次循環的執行順序都不能確定。

總結

  1. 每個線程默認有一個名字,儘管上面的例子中沒有指定線程對象的name,但是python會自動爲線程指定一個名字。
  2. 當線程的run()方法結束時該線程完成。
  3. 無法控制線程調度程序,但可以通過別的方式來影響線程調度的方式。

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]
'''

要點總結:

  1. 在一個函數中對全局變量進行修改時,如果是不可變對象包括int,float,string,tuple等,則需要對該全局變量用global聲明
  2. 如果該全局變量是可變對象包括list,dict,自定義類的實例等,則不需要用global進行聲明。
  3. 在一個進程內的所有線程共享全局變量,很方便在多個線程間共享數據
  4. 缺點就是,線程是對全局變量隨意遂改可能造成多線程之間對全局變量的混亂(即線程非安全)

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
'''
  • 如果多個線程同時對同一個全局變量操作,會出現資源競爭問題,從而數據結果會不正確。所以對於多線程共享全局變量存在一定的風險。當然後續有相應的解決方案,如鎖的實現。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章