進程、線程、協程和GIL(二)

    上一篇博客講了進程、線程、協程和GIL的基本概念,這篇咱們來講說在如下三點:html

  1> python中使用threading庫來建立線程的兩種方式python

  2> 使用Event對消來判斷線程是否已啓動微信

  3> 使用Semaphore和BoundedSemaphore兩個類分別來控制線程的併發數以及兩者之間的區別。併發

  若是想要了解基本概念,請移步個人上一篇博客:http://www.javashuo.com/article/p-qbymlkcy-o.htmlapp

正文:函數

  利用threading庫來在建立一個線程:ui

from threading import Thread

def run(name):
    print('我是: %s' % (name))
    
if __name__ == '__main__':
    t = Thread(target=run, args=('線程1號',))
    t.start()

  運行結果:  spa

  

  首先,建立一個Thread線程,並用target=run來指定這個線程須要執行那個run方法,而後將run方法所需的參數以args參數的形式傳遞過去,操作系統

  請注意,args所傳的參數是一個元組(tuple)類型,所以即便元組中只有一個參數,也要在這個參數後再加一個逗號,如 args=('線程1號',)線程

  

  並且,當咱們建立一個線程對象後,這個線程並不會當即執行,除非調用它的star()方法,當調用star()方法後,這個線程會調用你使用target傳給他的函數,

  並將args中的參數傳遞給這個函數。

  Python中的線程會在一個單獨的系統級線程中執行(如一個POSIX線程或一個Windows線程),這些線程所有由操做系統進行管理,線程一旦啓動,

  它將獨立運行直至目標函數運行結束。

  咱們能夠調用is_alive()方法來進行判斷該線程是否在運行(is_alive()方法返回True或者False):

  咱們知道,進程是依賴於線程來執行的,因此當咱們的py文件在執行時,咱們能夠理解爲有一個主線程在執行,當咱們建立一個子線程時,就至關於當前程序一共有兩個線程在執行。當子線程被建立後,主線程和子線程就獨立運行,相互並不影響。

  代碼以下:

 1 import time
 2 from threading import Thread
 3 
 4 def run(name):
 5     time.sleep(2)
 6     print('我是: %s' % (name))
 7 
 8 if __name__ == '__main__':
 9     t = Thread(target=run, args=('子線程',))
10     t.start()
11     print('我是主線程')

  執行結果:

  

  在代碼的第9行,建立了一個線程t,讓它來執行run()方法,這時,程序中就有了兩個線程同時存在、各自獨立運行,默認的,主線程是不會等待子線程的運算結果的,因此主線程繼續向下執行,打印L「我是主線程」,而子線程在調用run()方法時sleep了兩秒鐘,以後纔打印出「我是子線程」

  固然,咱們能夠手動的調用join()方法來讓主線程等待子線程運行結束後再向下執行:

 1 import time
 2 from threading import Thread
 3 
 4 def run(name):
 5     time.sleep(2)
 6     print('我是: %s' % (name))
 7 
 8 if __name__ == '__main__':
 9     t = Thread(target=run, args=('子線程',))
10     t.start()
11     t.join()
12     print('我是主線程')

  

  這時,程序在進行到第十行後,主線程就卡住了,它在等待子線程運行結束,檔子線程運行結束後,主線程纔會繼續向下運行,直至程序退出。

  可是,可是,不管主線程等不等待子線程,Python解釋器都會等待全部的線程都終止後纔會退出。也就是說,不管主線程等不等待子線程,這個程序最終都

  會運行兩秒多,由於子線程sleep了兩秒。

  因此,當遇到須要長時間運行的線程或者是須要一直在後臺運行的線程時,能夠將其設置爲後臺線程(守護線程)daemon=True,如:

t = Thread(target=run, args=('子線程',), daemon=True)

  守護線程,顧名思義是守護主線程的線程,他們是依賴於主線程而存在的,當主線程執行結束後,守護線程會被當即註銷,不管該線程是否執行結束。

  固然,咱們也能夠利用join()來使主線程等待主線程。

  使用threading庫來建立線程還有一種方式:

from threading import Thread

class CreateThread(Thread):

    def __init__(self):
        super().__init__()

    def run(self):
        print('我是子線程!')

t = CreateThread()
t.start()

  在開始t = Thread(target=run, args=('子線程',))這種方式調用方法時子線程調用的run方法的這個方法名是我隨便起的,實際上叫什麼都行,

  可是以繼承Thread類方式實現線程時,線程調用的方法名必須是run() 這個是程序寫死的。

 

   使用Event對象判斷線程是否已經啓動

   threading庫中的Event對象包含一個可由線程來設置的信號標誌,它容許線程等待某些事件的發生。

  初始狀態時,event對象中的信號標誌被設置爲假,若是有一個線程等待event對象,且這個event對象的標誌爲假,那麼這個線程就會一直阻塞,直到該標誌爲真。若是將一個event對象的標誌設置爲真,他將喚醒全部等待這個標誌的線程,若是一個線程等待一個被設置爲真得Event對象,那麼它將忽略這個事件,繼續向下執行。  

  Event (事件) 定義了一個全局的標誌Flag,若是Flag爲False,當程序執行event.wait()時就會阻塞,當Flag爲True時,程序執行event.wait()時便不會阻塞:

  event.set():  將標誌Flag設置爲True, 並通知全部因等待該標誌而處於阻塞狀態的線程恢復運行。

  event.clear(): 將標誌Flag設置爲False

  event.wait():  判斷當前標誌狀態,若是是True則當即返回,不然線程繼續阻塞。

  event.isSet(): 獲取標誌Flag狀態: 返回True或者False

 1 from threading import Thread, Event
 2 
 3 def run(num, start_evt):
 4     if int(num) >10:
 5         start_evt.set()
 6     else:
 7         start_evt.clear()
 8 start_evt = Event()
 9 
10 if __name__ == '__main__':
11     num = input("請輸入數字>>>")
12     t = Thread(target=run, args=(num, start_evt,))
13     t.start()
14     start_evt.wait()  # 主線程獲取Event對象的標誌狀態,若爲True,則主線程繼續執行,不然,主線程阻塞
15     print("主線程繼續執行!")

  上邊這段代碼:當輸入的數字大於10時,將標誌設置爲True,主程序繼續執行,當小於或者等於10時,將標誌設爲False(默認爲False),主線程阻塞。

     

  值得注意的是:當Event對象的標誌被設置爲True時,他會喚醒全部等待他的線程,若是隻想喚醒某一個線程,最好使用信號量。

 

  信號量

  信號量,說白了就是一個計數器,用來控制線程的併發數,每次有線程得到信號量的時候(即acquire())計數器-1,釋放信號量時候(release())計數器+1,計數器爲0的時候其它線程就被阻塞沒法得到信號量

  acquire()   # 設置一個信號量

  release()   # 釋放一個信號量

  python中有兩個類實現了信號量:(Semaphore和BoundedSemaphore)

  Semaphore和BoundedSemaphore的相同之處

    經過: threading.Semaphore(3) 或者 threading.BoundedSemaphore(3) 來設置初始值爲3的計數器

    執行acquire() 計數器-1,執行release() 計數器+1,當計數器爲0時,其餘線程均沒法再得到信號量從而阻塞

import threading

se = threading.BoundedSemaphore(3)

for i in range(5):
    se.acquire()
    print('信號量被設置')

for j in range(10):
    se.release()
    print('信號量被釋放了')

  執行結果:   

import threading

se = threading.Semaphore(3)

for i in range(5):
    se.acquire()
    print('信號量被設置')

for j in range(10):
    se.release()
    print('信號量被釋放了')

  執行結果:

  能夠看到,不管咱們用那個類建立信號量,當計數器被減爲0時,其餘線程均會阻塞。

  這個功能常常被用來控制線程的併發數

  沒有設置信號量:

import time
import threading

num = 3

def run():
    time.sleep(2)
    print(time.time())

if __name__ == '__main__':
    t_list = []
    for i in range(20):
        t = threading.Thread(target=run)
        t_list.append(t)
    for i in t_list:
        i.start()

  執行結果:20個線程幾乎在同時執行,,若是主機在執行IO密集型任務時執行這種程序時,主機有可能會宕機,

  可是在設置了信號量時,咱們能夠來控制同一時間同時運行的線程數:

import time
import threading

num = 3

def run():
    se.acquire()  # 添加信號量
    time.sleep(2)
    print(time.time())
    se.release()  #  釋放一個信號量


if __name__ == '__main__':
    t_list = []
    se = threading.Semaphore(5)  # 設置一個大小爲5的計數器(同一時間,最多容許5個線程在運行)
   # se = threading.BoundedSemaphore(5) 
    for i in range(20):
        t = threading.Thread(target=run)
        t_list.append(t)
    for i in t_list:
        i.start()

  這時,咱們給程序加上信號量,控制它在同一時間內,最多隻有5個線程在運行。

  

  二者之間的差別性:

    當計數器達到設定好的上線時,BoundedSemaphore就沒法進行release()操做了,Semaphore沒有這個限制,它會拋出異常。

  

import threading


se = threading.Semaphore(3)

for i in range(3):  # 將計數器值減爲0
    se.acquire(3)

for j in range(5):  # 將計數器值加至5
    se.release()
    print('信號量被釋放了')

  運行結果:

  

import threading


se = threading.BoundedSemaphore(3)

for i in range(3):  # 將計數器值減爲0
    se.acquire(3)

for j in range(5):  # 將計數器值加至5
    se.release()
    print('信號量被釋放了')

  運行結果:

  拋異常了:信號量被釋放太屢次。。。

   好了,這篇文章的就先寫到這裏,下一篇文章我會講解關於線程間通訊、線程加鎖等問題

想了解更多Python關於爬蟲、數據分析的內容,歡迎你們關注個人微信公衆號:悟道Python

  

相關文章
相關標籤/搜索