python多線程基礎

本文首發於知乎python

多線程理解編程

多線程是多個任務同時運行的一種方式。好比一個循環中,每一個循環看作一個任務,咱們但願第一次循環運行還沒結束時,就能夠開始第二次循環,用這種方式來節省時間。多線程

python中這種同時運行的目的是最大化利用CPU的計算能力,將不少等待時間利用起來。這也說明若是程序耗時不是由於等待時間,而是任務很是多,就是要計算那麼久,則多線程沒法改善運行時間。app

更多有關多線程理解的內容能夠參考下面資料函數

簡單使用ui

先看下面這個函數lua

import time
def myfun():
time.sleep(1)
a = 1 + 1
print(a)
複製代碼

若是咱們要運行10次這個函數,它的運行時間主要在於每次sleep的那一秒,1 + 1的計算是不會耗多少時間的。這種狀況能夠用多線程提升效率。spa

下面來看一下不使用多線程耗時和使用多線程的耗時線程

不使用多線程code

t = time.time()
for _ in range(5):
myfun()
print(time.time() - t)
複製代碼

獲得結果是 5.002434492111206

下面咱們使用多線程

from threading import Thread
for _ in range(5):
th = Thread(target = myfun)
th.start()
複製代碼

這樣使用多線程其實就能夠了,你會發現大概1秒,5個2會同時出來,說明5次循環其實幾乎同時運行,5次1秒的等待時間同時進行,最後只等待了1秒。

這裏多線程只包括了兩步

  • Thread增長一個線程,這裏是將每一次循環做爲一次新的線程,一個線程執行一次myfun函數。
  • start()開始運行這個線程,每一個線程都須要這樣顯式開啓纔會運行。一個線程這樣開啓後,不須要等待它運行完成,就能夠繼續運行下面的程序,即下一次循環(而後又新建了第二個線程,運行未結束即開啓第三個……)

這裏要注意一點:多線程是放在循環裏面的,不能定義好循環以後,從外面將它變成多線程。

讀者可能會注意到,不用多線程時是經過程序計算時間的,使用多線程卻沒有。這是由於要計算時間須要增長一些代碼,沒法展現最簡單的多線程使用,因此就先不計算時間。接下來咱們就講join()的使用,並計算時間。

join的使用

線程的join()方法表示等這個線程運行完畢,程序再往下運行。咱們來看下面的例子

from threading import Thread
t = time.time()
for _ in range(5):
th = Thread(target = myfun)
th.start()
th.join()
print(time.time() - t)
# 結果爲 5.0047078132629395
複製代碼

這裏start()以後立刻join(),表示每個線程都要運行結束才能進行下一次循環,這樣就和沒有使用多線程沒有區別了。不過若是要計算多線程運行時間倒是要用到這個join()

咱們先看一下不用join()的狀況

from threading import Thread
t = time.time()
for _ in range(5):
th = Thread(target = myfun)
th.start()
print(time.time() - t)
# 結果爲 0.0009980201721191406
複製代碼

它連1秒都沒有等,就輸出告終果,並且5個2是在打印出這個以後才輸出出來的。這是由於print(time.time() - t)是區別於那5次循環線程以外的第6個線程,它不會等待5個線程運行結束就會開始運行。因此這樣是沒法得到上面5個線程的運行時間的,咱們須要用join()等待5個線程都運行結束。

代碼以下

from threading import Thread
t = time.time()
ths = []
for _ in range(5):
th = Thread(target = myfun)
th.start()
ths.append(th)
for th in ths:
th.join()
print(time.time() - t)
# 結果爲 1.0038363933563232
複製代碼

上面定義ths列表存儲這些線程,最後用循環確保每個線程都已經運行完成再計算時間差。

join()不僅是用於這種情形。當一步代碼運行依賴以前代碼運行完成時,就要加入join()命令。

如今咱們已經學完了多線程的通常使用方法,能夠在多數場景使用了。下面來介紹一些細節

其餘

(1)線程名稱

咱們直接看下面的代碼

import threading
print(threading.current_thread().getName())
def myfun():
time.sleep(1)
print(threading.current_thread().name)
a = 1 + 1
for i in range(5):
th = threading.Thread(target = myfun, name = 'thread {}'.format(i))
th.start()
# 輸出結果
MainThread
thread 0
thread 1
thread 4
thread 3
thread 2
複製代碼

解釋一下

  • threading.current_thread()表示當前線程,能夠調用namegetName()獲取線程名稱
  • 任何進程都會默認啓動一個線程,默認名稱爲MainThread,也就是主程序佔一個線程,這個線程和以後用Thread新加的線程是相互獨立的,主線程不會等待其他線程運行結束就會繼續往下運行。以前不用join()沒法計算運行時間就是由於主線程先運行完了。
  • Thread表示運行這個函數啓動一個新的線程,在其中加一個name參數指定這個函數線程名,則在這個函數內打印線程名就顯示這裏name參數對應值
  • 在循環中打印有兩種。第一種print(threading.current_thread().name)則是MainThread;第二種print(th.name)則是thread 1

(2)Thread函數

上面咱們使用了Thread函數的target name參數,下面來講一下它的其餘參數

  • args指定target對應函數的參數,用元組傳入,好比args = (3, )
  • daemon主線程默認是False,若是沒有指定則繼承父線程的值。True則若是主線程運行結束,該線程也中止運行;False則該線程會繼續運行直到運行結束,無視主線程如何。(要看這個參數的效果要在py文件中編寫代碼,在cmd裏運行,不能在jupyter notebook裏,由於這裏會多出一些線程干擾)
  • group是預留的一個參數,用於之後擴展ThreadGroup類,如今沒用

(3)Thread對象

上面threading.Threadthreading.current_thread()都建立了一個Thread對象,Thread對象有以下屬性和方法

  • getName() .name 獲取線程名
  • setName() 設置線程名
  • start() join()這兩個以前說過了
  • join()有一個timeout參數,表示等待這個線程結束時,若是等待時間超過這個時間,就再也不等,繼續進行下面的代碼,可是這個線程不會被中斷
  • run() 也是運行這個線程,可是必須等到這個線程運行結束纔會繼續執行以後的代碼(若是將上面的start全換成run則至關於沒有開多線程)
  • is_alive()若是該線程還沒運行完,就是True不然False
  • daemon 返回該線程的daemon
  • setDaemon(True)設置線程的daemon

(4)threading

一些直接調用的變量

  • threading.currentThread(): 返回當前的線程變量
  • threading.enumerate(): 返回一個包含正在運行的線程的list
  • threading.activeCount(): 返回正在運行的線程數量,與len(threading.enumerate())有相同的結果

歡迎關注個人知乎專欄

專欄主頁:python編程

專欄目錄:目錄

版本說明:軟件及包版本說明

相關文章
相關標籤/搜索