Python最詳細的零基礎入門之——多線程詳解!

進程 && 線程

進程:是內存中的一個獨立的句柄,我們可以理解為一個應用程序在內存中就是一個進程。 各個進程之間是內存相互獨立,不可共享的

線程:每個應用運行之後就會對應啟動一個主線程,通過主線程可以創建多個字線程,各個線程共享主進程的內存空間。

關於線程、進程的解釋有一篇有趣而生動的解釋(http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html)

GIL(全局解釋器鎖)

我們知道多進程(mutilprocess) 和 多線程(threading)的目的是用來被多顆CPU進行訪問, 提高程序的執行效率。 但是在python內部存在一種機制(GIL),在多線程 時同一時刻只允許一個線程來訪問CPU。

GIL 並不是Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。就好比C++是一套語言(語法)標準,但是可以用不同的編譯器來編譯成可執行代碼。有名的編譯器例如GCC,INTEL C++,Visual C++等。

Python也一樣,同樣一段代碼可以通過CPython,PyPy,Psyco等不同的Python執行環境來執行。像其中的JPython就沒有GIL。然而因為CPython是大部分環境下默認的Python執行環境。所以在很多人的概念裡CPython就是Python,也就想當然的把 GIL 歸結為Python語言的缺陷。所以這裡要先明確一點:GIL並不是Python的特性,Python完全可以不依賴於GIL。

雖然python支持多線程,但是由於GIL的限制,在實際運行時,程序運行後開啟多個線程,但在通過GIL後同時也只能有一個線程被CPU執行。

多線程

1)多線程執行方法

import timefrom threading import Threaddef do_thread(num): print("this is thread %s" % str(num)) time.sleep(3)for i in range(5): t = Thread(target=do_thread, args=(i,)) t.start()

以上方法就開啟了一個5個線程,target用來定義開啟線程後要執行的方法,args為參數

線程的其它方法:

1 setName(), getName()

setName(): 給線程設置一個名字

getName(): 獲取線程的名稱

import timefrom threading import Threaddef do_thread(num): print("this is thread %s" % str(num)) time.sleep(3)for i in range(2): t = Thread(target=do_thread, args=(i,)) t.start() t.setName("Mythread_{0}".format(str(i))) print(t.getName())run result: this is thread 0 Mythread_0 this is thread 1 Mythread_1

2 setDaemon()

setDaemon(True/False): 設置創建的子線程為前臺線程或後臺線程.設置為True則子線程為後臺線程。線程默認為前臺線程(不設置此方法)

前臺線程: 當子線程創建完成後,主線程和子線程(前臺線程)同時運行,如果主線程執行完成,而子線程還未完成則等待子線程執行完成以後整個程序才結束。

後臺線程: 當子線程創建完成後,如果子線程還未結束,而主線程運行結束則不管子線程了,程序就結束。

此方法設置必須在 start() 方法前進行設置, 看代碼:

import timefrom threading import Threaddef do_thread(num): print("this is thread %s" % str(num)) time.sleep(3) print("OK", str(num))for i in range(2): t = Thread(target=do_thread, args=(i,)) # 不設置此方法默認前臺線程, #t.setDaemon(True) t.setName("Mythread_{0}".format(str(i))) t.start() print(t.getName())run result:this is thread 0Mythread_0this is thread 1Mythread_1OK 0OK 1import timefrom threading import Threaddef do_thread(num): print("this is thread %s" % str(num)) time.sleep(3) # 執行到此時主線程執行完了,程序結束,下面的代碼不會執行 print("OK", str(num))for i in range(2): t = Thread(target=do_thread, args=(i,)) # 設置線程為後臺線程 t.setDaemon(True) t.setName("Mythread_{0}".format(str(i))) t.start() print(t.getName())run result:this is thread 0Mythread_0this is thread 1Mythread_1

3 join()

join(timeout) : 多線程的 wait(),當主線程執行 子線程.join() 方法後,主線程將等待子線程執行完再接著執行。當加上timeout參數後,如果超過timeout時間不管子線程有沒有執行完都將結束等待

看下面兩個例子

import timefrom threading import Threaddef do_thread(num): time.sleep(3) print("this is thread %s" % str(num))for i in range(2): t = Thread(target=do_thread, args=(i,)) t.setName("Mythread_{0}".format(str(i))) t.start() print("print in main thread: thread name:", t.getName())run result: print in main thread: thread name: Mythread_0 print in main thread: thread name: Mythread_1 this is thread 0 this is thread 1 

上面無join方法時,主線程執行完print,等待子線程函數中的print執行完成,這個程序退出。 下面我們看看加上join方法後的效果

import timefrom threading import Threaddef do_thread(num): time.sleep(3) print("this is thread %s" % str(num))for i in range(2): t = Thread(target=do_thread, args=(i,)) t.setName("Mythread_{0}".format(str(i))) t.start() t.join() print("print in main thread: thread name:", t.getName())run result:this is thread 0print in main thread: thread name: Mythread_0this is thread 1print in main thread: thread name: Mythread_1

當程序運行到join後,將等待子程序執行完成,然後才向下執行。這樣真個程序就變成一個單線程的順序執行了。多線程就沒什麼鳥用了。

join()與setDaemon()都是等待子線程結束,有什麼區別呢:

當執行join()後主線程就停了,直到子線程完成後才開始接著主線程執行,整個程序是線性的

setDaemon() 為前臺線程時,所有的線程都在同時運行,主線程也在運行。只不過是主線程運行完以後等待所有子線程結束。這個還是一個並行的執行,執行效率肯定要高於join()方法的。

4 線程鎖

線程是內存共享的,當多個線程對內存中的同一個公共變量進行操作時,會導致線程爭搶的問題,為了解決此問題,可以使用線程鎖。

import timeimport threadingdef do_thread(num): global public_num # 加鎖 lock.acquire() public_num -= 1 # 解鎖 lock.release() time.sleep(1) print("public_num in thread_%s is %s" % (str(num), str(public_num)))public_num = 100threads_list = []lock = threading.Lock()for i in range(50): t = threading.Thread(target=do_thread, args=(i,)) t.setName("Mythread_{0}".format(str(i))) t.start() threads.append(t) # 等待所有子線程結束for t in threads: t.join()print("last result of public_num is ", public_num)

5 event()

線程的事件, 用於主線程控制子線程的執行。它的本質就是定義了一個全局的flag標識,並通過一些方法來獲取、設置此標識。包括:

wait()方法:當flag標識為False時,wait()方法將阻塞,為True時,wait()不阻塞

set()方法:設置flag標識為True

clear()方法: 設置flag標識為False

初始化時flag標識為False(阻塞狀態)

is_set()/isSet() : 判斷當前flag標識是否為True

import threadingdef do(event): print('start') # 默認初始化狀態為False,到這裡就阻塞了 event.wait() print('execute\n')if __name__ == "__main__": event_obj = threading.Event() for i in range(10): t = threading.Thread(target=do, args=(event_obj,)) t.start() inp = input('input:') if inp == 'true': # 如果為true,則flag=True,不阻塞,子進程繼續運行 event_obj.set() else: event_obj.clear()

event一個模擬紅綠燈的實例:

def light(): linght_time = 0 if not event.is_set(): event.set() # Flag = True, 阻塞 while True: time.sleep(1) if linght_time < 10: print("Green is on....") elif linght_time < 13: print("Yellow is on ....") elif linght_time < 16: print("Red is on ......") if event.is_set(): event.clear() else: # 大於16, 該重新調綠燈了 linght_time = 0 event.set() linght_time += 1def car_run(carnum): while True: time.sleep(2) if event.is_set(): print("car %s is run" % carnum) else: print("CAR %s IS WAITTING........" % carnum)if __name__ == "__main__": event = threading.Event() l = threading.Thread(target=light, ) l.start() for i in range(3): c = threading.Thread(target=car_run, args=(str(i), )) c.start()

6) Semaphore()

Semaphore信號量管理一個內置的計數器:

每當調用acquire()時內置計數器-1;

調用release() 時內置計數器+1;

計數器不能小於0;當計數器為0時,acquire()將阻塞線程直到其他線程調用release()。

import threadingimport timedef do(): semaphro.acquire() print("this is {0} set the semaphore".format(threading.current_thread().getName())) time.sleep(2) semaphro.release() print("\033[1;30mthi is {0} release the semaphore\033[0m".format(threading.current_thread().getName()))if __name__ == "__main__": semaphro = threading.Semaphore(2) for i in range(10): t = threading.Thread(target=do) t.setName("Thread_{0}".format(str(i))) t.start() print("finished")

上例中,雖然創建了10個線程,但同時只有2個線程在運行,就是因為在線程中通過Semaphore設置了2個信號量。只有其中一個釋放後另其它的線程再能開始執行


分享到:


相關文章: