進程是系統進行資源分配最小單元,線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.進程在執行過程當中擁有獨立的內存單元,而多個線程共享內存等資源。html
import threading from threading import Thread def test(x): print('this is {}'.format(x)) time.sleep(2) def get_thread(number=5): l_thread = (Thread(target=test, args=(i,)) for i in range(number)) for t in l_thread: print(t) t.start() # 啓動線程開始執行 print(len(threading.enumerate())) if __name__ == '__main__': get_thread(5) # 結果 <Thread(Thread-1, initial)> this is 0 <Thread(Thread-2, initial)> this is 1 <Thread(Thread-3, initial)> this is 2 <Thread(Thread-4, initial)> this is 3 <Thread(Thread-5, initial)> this is 4 6
經過以上可知,咱們只須要建立一個Thread對象,並運行start方法,解釋器就會建立一個子進程執行咱們的target,咱們建立了5個線程,可是使用threading.enumerate查看線程的數量發現有6個線程,由於當前在執行的還有一個主線程。主線程會默認等待全部的子線程結束後再結束。ide
import threading from threading import Thread class MyThread(Thread): def __init__(self, x): super().__init__() self.x = x def run(self): print('this is {}'.format(self.x)) time.sleep(2) def get_thread1(number=5): l_thread = (MyThread(i) for i in range(number)) for t in l_thread: print(t.name) t.start() print(len(threading.enumerate())) if __name__ == '__main__': get_thread1(5)
Thread對象有一個run方法,它就是咱們須要執行的目標函數,因此咱們能夠經過繼承Thread對象,重寫run方法,將咱們的目標代碼放置在run方法中。函數
class Thread: def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None): pass # Thread類是python用來建立線程的類, group:擴展保留字段; target:目標代碼,通常是咱們須要建立線程執行的目標函數。 name:線程的名字,若是不指定會自動分配一個; args:目標函數的普通參數; kwargs:目標函數的鍵值對參數; daemon:設置線程是否爲守護線程,便是前臺執行仍是後臺執行,默認是非守護線程,當daemon=True時,子線程爲守護線程,此時主線程不會等待子線程,若是主線程完成會強制殺死全部的子線程而後退出。 # 方法 start():建立一個子線程並執行,該方法一個Thread實例只能執行一次,其會建立一個線程執行該類的run方法。 run():子線程須要執行的代碼; join():主線程阻塞等待子線程直到子線程結束才繼續執行,能夠設置等待超時時間timeout. ident():線程標識符,線程未啓動以前爲None,啓動後爲一個int; is_alive():查看子線程是否還活着你返回一個布爾值。 daemon:判斷是不是守護線程;
多個線程之間能夠共享內存等資源,使得多個線程操做同一份資源的時候可能致使資源發生破壞,即線程非安全。ui
number = 100 class MyThread(Thread): def run(self): for i in range(1000000): global number number += 1 print(number) def get_thread1(number=5): l_thread = (MyThread() for i in range(number)) for t in l_thread: t.start() if __name__ == '__main__': get_thread1(5) # 結果 1439426 1378835 2241060 2533150 3533150
上例可知,若是是同步運算的話,最終number的結果應該爲5000100,但顯然不是。緣由是若是線程1取得number=100時,線程切換到線程2,又取得number=100,加1賦值給number=101;若是,又切換回線程1,number加1也是101;至關於執行了兩次加1的操做,然而number=101.這就是多線程的線程非安全!
怎麼解決這個問題呢?咱們看到上述代碼中number += 1是核心代碼,這個地方隨意切換線程就會形成數據破壞,所以只要咱們可以設置代碼每次執行到這裏的時候不容許切換線程就好了。這就是鎖的由來。
用鎖加入上述代碼:
number = 100 mutex = threading.Lock() # 建立鎖對象 class MyThread(Thread): def run(self): global number for i in range(1000000): y = mutex.acquire() # 獲取鎖 if y: # 拿到鎖就執行下面 number += 1 mutex.release() # 釋放鎖 print(number) def get_thread1(number=5): l_thread = (MyThread() for i in range(number)) for t in l_thread: t.start() if __name__ == '__main__': get_thread1(5) # 結果: 4481177 4742053 4869413 4973771 5000100
可知最後的結果符合預期,threading模塊中定義了Lock類,能夠很方便實現鎖機制,每次執行核心代碼以前先去獲取鎖,拿到了才能執行,拿不到默認阻塞等待。
#建立鎖 mutex = threading.Lock() #鎖定 mutex.acquire(blocking=True) # blocking=True,默認線程阻塞等待;若是blocking=False,線程不會等待,即上例中y會返回False,繼續執行下面的代碼,最後的結果不會符合預期 #釋放 mutex.release()
加鎖以後,鎖住的那段代碼變成了單線程,阻止了多線程併發執行,效率降低了;
鎖能夠有多個,若是不一樣的線程持有不一樣的鎖並相互等待的話,就會形成死鎖;
python的多線程問題遠不止如此,還有一個歷史遺留問題-全局鎖。
若是一段代碼存在兩個鎖的話,可能會出現死鎖現象,一旦出現死鎖,系統就會卡死。
number = 100 mutex1 = threading.Lock() # 建立鎖對象 mutex2 = threading.Lock() class MyThread1(Thread): def run(self): global number for i in range(1000): if mutex1.acquire(): # 拿到鎖就執行下面 number += 1 if mutex2.acquire(): print('this is mutex2') mutex2.release() mutex1.release() # 釋放鎖 print(number) class MyThread2(Thread): def run(self): global number for i in range(1000): if mutex2.acquire(): # 拿到鎖就執行下面 number += 1 if mutex1.acquire(): print('this is mutex2') mutex1.release() mutex2.release() # 釋放鎖 print(number) def get_thread1(): l_thread = (MyThread1(), MyThread2()) for t in l_thread: t.start() if __name__ == '__main__': get_thread1()
通常解決死鎖的辦法是儘可能不使用多個鎖,或設計程序時避免死鎖,或爲鎖添加超時等待。
全局鎖的前世此生不是一兩句話能講完的。可參考:Python全局解釋器鎖
總結一下就是:
number = 100 number1 = 100 mutex = threading.Lock() class MyThread(Thread): def run(self): global number t1 = time.time() for i in range(1000000): y = mutex.acquire() # 獲取鎖 if y: # 拿到鎖就執行下面 number += 1 mutex.release() # 釋放鎖 t2 = time.time() print(t2-t1) def get_thread1(number=5): l_thread = (MyThread() for i in range(number)) for t in l_thread: t.start() def get_thread2(n=5): global number1 for i in range(1000000*n): number1 += 1 print(number1) if __name__ == '__main__': get_thread1() t2 = time.time() get_thread2() t3 = time.time() print(t3-t2)
可知多線程的執行時間遠遠大於單線程。
python最好避免使用多線程,而用多進程代替多線程;
協程是多線程的很好的替代方案。
參考: