python多線程

多線程

任務能夠由多進程完成,也能夠由一個進程內的多線程完成。
咱們前面提到了進程是由若干線程組成的,一個進程至少有一個線程。
因爲線程是操做系統直接支持的執行單元,所以,高級語言一般都內置多線程的支持,Python也不例外,而且,Python的線程是真正的Posix Thread,而不是模擬出來的線程。Python的標準庫提供了threading模塊。html

建立

在python中多線程的實現方法有2種。python

  1. 將要執行的方法做爲參數傳遞給Thread的構造方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import threading
    
    def worker(args):
        print("開始子進程 {0}".format(args))
        print("結束子進程 {0}".format(args))
    
    if __name__ == '__main__':
    
        print("start main")
        t1 = threading.Thread(target=worker, args=(1,))
        t2 = threading.Thread(target=worker, args=(2,))
        t1.start()
        t2.start()
        print("end main")

輸出結果多線程

1
2
3
4
5
6
start main
開始子進程 1
結束子進程 1
開始子進程 2
結束子進程 2
end main

 

  1. 從Thread繼承,並重寫run()方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import threading
    import time
    
    class Hello(threading.Thread):
        def __init__(self, args):
            super(Hello, self).__init__()    #注意:要顯式的調用父類的初始化函數
            self.args = args
    
        def run(self):
            print("開始子進程 {0}".format(self.args))
            time.sleep(1)
            print("結束子進程 {0}".format(self.args))
    
    if __name__ == '__main__':
        a = 1
        print("start main")
        t1 = Hello(1)
        t2 = Hello(2)
        t1.start()
        t2.start()
        print("end main")

輸出結果併發

1
2
3
4
5
6
start main
開始子進程 1
開始子進程 2
end main
結束子進程 1
結束子進程 2

 

模塊函數

  • threading.active_count()
    返回當前存活的thread對象的數量。返回的數量和enumerate()列表長度相等。
  • threading.current_thread()
    返回當前線程對象。
  • threading.get_ident()
    返回當前線程的thread identifier。這是個非零的整數。該值無特殊含義。
  • threading.enumerate()
    以列表形式返回當前存活的全部線程對象。列表中包括守護線程和由current_thread()建立的虛擬線程對象,不包括已結束和還沒開始的線程。
  • threading.main_thread()
    返回主線程對象。正常狀況下,主線程由python解釋器啓動。

    對象方法

  • start()
    啓動線程,調用run方法。同一線程對象屢次調用將會出現RuntimeError錯誤。
  • run()
    啓動線程
  • join(timeout=None)
    等待線程對象結束。
  • is_alive()
    線程是否活着

    對象屬性

  • name
    線程的名稱,無特殊意義。
  • daemon
    布爾值,表示該線程是否爲守護線程。設置該值必須在調用run()方法前,不然會報RuntimeError錯誤。假如主線程不是守護線程,該主線程建立的全部子線程該值爲False。

    線程鎖

    多線程和多進程最大的不一樣在於,多進程中,同一個變量,各自有一份拷貝存在於每一個進程中,互不影響,而多線程中,全部變量都由全部線程共享,因此,任何一個變量均可以被任何一個線程修改,所以,線程之間共享數據最大的危險在於多個線程同時改一個變量,把內容給改亂了。
    這種狀況下經過線程鎖保證修改不會發生衝突。
    鎖的建立經過threading.Lock()進行,操做相似於多進程。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import threading
    import time
    
    def worker(name, lock):
        with lock:
            print("start {0}".format(name))
            time.sleep(5)
            print("end {0}".format(name))
    
    if __name__ == "__main__":
        lock = threading.Lock()
        t1 = threading.Thread(target=worker, args=("worker1", lock))
        t2 = threading.Thread(target=worker, args=("worker2", lock))
        t1.start()
        t2.start()

輸出結果app

1
2
3
4
start worker1
end worker1
start worker2
end worker2

 

固然,獲取鎖也能夠經過lock.acquire()完成,釋放鎖經過lock.release()完成。ide

1
2
3
4
5
6
7
8
def worker(name, lock):
    lock.acquire()
    try:
        print("start {0}".format(name))
        time.sleep(5)
        print("end {0}".format(name))
    finally:
        lock.release()

 

  • 鎖的好處
    確保了某段關鍵代碼只能由一個線程從頭至尾完整地執行。
  • 壞處
    阻止了多線程併發執行,包含鎖的某段代碼實際上只能以單線程模式執行,效率就大大地降低了。
    因爲能夠存在多個鎖,不一樣的線程持有不一樣的鎖,並試圖獲取對方持有的鎖時,可能會形成死鎖,致使多個線程所有掛起,既不能執行,也沒法結束,只能靠操做系統強制終止。

    線程共享內存

    多線程和多進程不一樣之處在於多線程自己就是能夠和父進程共享內存的,這也是爲何其中一個線程掛掉之後,爲何其餘線程也會死掉的道理。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import threading
    
    l = list()
    l += range(1, 4)
    
    def worker():
        l.append("GOD")
    
    if __name__ == "__main__":
        t1 = threading.Thread(target=worker)
        t2 = threading.Thread(target=worker)
        t1.start()
        t2.start()
        print(l)

輸出結果函數

1
[1, 2, 3, 'GOD', 'GOD']

 

線程池

在使用多線程處理任務時也不是線程越多越好,因爲在切換線程的時候,須要切換上下文環境,依然會形成cpu的大量開銷。爲解決這個問題,線程池的概念被提出來了。預先建立好一個較爲優化的數量的線程,讓過來的任務馬上可以使用,就造成了線程池。
此處介紹threapool是一個第三方模塊,須要使用pip install threadpool安裝。優化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import threadpool

def hello(m, n, o):
    print("m = {0}  n={1}  o={2}".format(m, n, o))

if __name__ == '__main__':
    lst_vars_1 = ['1', '2', '3']
    lst_vars_2 = ['4', '5', '6']
    func_var = [(lst_vars_1, None), (lst_vars_2, None)]

    pool = threadpool.ThreadPool(2)
    requests = threadpool.makeRequests(hello, func_var)
    [pool.putRequest(req) for req in requests]
    pool.wait()

 

輸出結果ui

1
2
m = 1  n=2  o=3
m = 4  n=5  o=6

 

  • Executor
    在python3.3以後,並行任務可使用Executor Objects,更多內容點擊concurrent.futures — Launching parallel tasks查看官方介紹。
  • 關於python中多核CPU的說明
    Python的線程雖然是真正的線程,但解釋器執行代碼時,有一個GIL鎖:Global Interpreter Lock,任何Python線程執行前,必須先得到GIL鎖,而後,每執行100條字節碼,解釋器就自動釋放GIL鎖,讓別的線程有機會執行。這個GIL全局鎖實際上把全部線程的執行代碼都給上了鎖,因此,多線程在Python中只能交替執行,即便100個線程跑在100核CPU上,也只能用到1個核。
    GIL是Python解釋器設計的歷史遺留問題,一般咱們用的解釋器是官方實現的CPython,要真正利用多核,除非重寫一個不帶GIL的解釋器。
    因此,在Python中,可使用多線程,但不要期望能有效利用多核。若是必定要經過多線程利用多核,那隻能經過C擴展來實現,不過這樣就失去了Python簡單易用的特色。
    Python雖然不能利用多線程實現多核任務,但能夠經過多進程實現多核任務。多個Python進程有各自獨立的GIL鎖,互不影響。
    更多關於threading的內容能夠點擊threading — Thread-based parallelism查看官方介紹。
相關文章
相關標籤/搜索