所謂串行,就比如咱們走路同樣,一條馬路,一次只能過一輛車,這樣速度就會很受限制。html
理解了串行,並行就更好理解了,就是好多條路。路越多,車流量就越大。python
多線程就是並行的一種。固然,實際發生在計算機內部的時候,並不能單純的理解爲多了一條路。由於咱們的計算機一個CPU核心,同時只能處理一個任務。在CPU只有一個核心的狀況下,多線程咱們就能夠理解爲開闢出了許多條道路,可是咱們的出口只有一個。每條路上面的車都會你爭我搶誰也不讓,哪條路搶到了通行權這條路上的汽車就會趕快經過,直到下條路搶到通行權,其餘路上的汽車都會進入等待狀態。實際發生在計算機內部的時候,線程之間的切換都是毫秒級的,因此人的沒法感受出來線程之間有等待的,在人看來全部的線程都是同時運行的。使用多線程,能夠大大的增長程序的性能和效率。web
# 導入Python標準庫中的Thread模塊 from threading import Thread # 建立一個線程 t = Thread(target=function_name, args=(function_parameter1, function_parameterN)) # 啓動剛剛建立的線程 t.start()
function_name: 須要線程去執行的方法名數據庫
args: 線程執行方法接收的參數,該屬性是一個元組,若是隻有一個參數也須要在末尾加逗號。數組
from threading import Thread # 建立一個類,必需要繼承Thread class MyThread(Thread): # 繼承Thread的類,須要實現run方法,線程就是從這個方法開始的 def run(self): # 具體的邏輯 function_name(self.parameter1) def __init__(self, parameter1): # 須要執行父類的初始化方法 Thread.__init__(self) # 若是有參數,能夠封裝在類裏面 self.parameter1 = parameter1 # 若是有參數,實例化的時候須要把參數傳遞過去 t = MyThread(parameter1) # 一樣使用start()來啓動線程 t.start()
在上面的例子中,咱們的主線程不會等待子線程執行完畢再結束自身。可使用Thread類的join()方法來子線程執行完畢之後,主線程再關閉。安全
from threading import Thread class MyThread(Thread): def run(self): function_name(self.parameter1) def __init__(self, parameter1): Thread.__init__(self) self.parameter1 = parameter1 t = MyThread(parameter1) t.start() # 只須要增長一句代碼 t.join()
上面的方法只有一個線程,若是有多個線程,能夠把每一個線程放在一個數組中。服務器
thread_list = [] for i in range(1, 11): t = MyThread(parameter1) thread_list.append(t) t.start() # 在這裏統一執行線程等待的方法 for t in thread_list: t.join()
在咱們的計算機中,須要大量用到CPU計算的事情,咱們稱爲CPU密集型操做。數據結構
如,咱們計算9的一億次方,這種大型的運算,或者是進行文件格式的轉換,這些都是屬於CPU密集型操做。多線程
注意:上面的運算會消耗很長的計算時間,有興趣能夠從小到大慢慢嘗試一下app
所謂IO密集型操做,就是涉及到大量的輸入輸出,好比頻繁的數據庫訪問,頻繁的web服務器訪問,這種狀況都屬於IO密集型操做。
咱們都知道,多線程最大的一個問題就是線程之間的數據同步問題。在計算機發展過程當中,各個CPU廠商,爲了提高本身的性能,引入了多核概念。可是多個核心之間若是作到數據同步讓全部人都花費了不少的時間和金錢,甚至最後消耗了CPU不少的性能才得以實現。
瞭解Python的朋友都知道,Python默認的實現是CPython,而CPython使用的是C語言的解釋器。而因爲歷史緣由,CPython中不幸的擁有了一個在將來很是影響Python性能的因素,那就是GIL。GIL全稱Global Interpreter Lock
,又叫全局解釋器鎖。GIL是計算機程序設計語言解釋器用於同步線程的工具,而CPython中正是支持了GIL的特性,使得Python的解釋器同一時間只能有一條線程運行,一直等到這個線程執行完畢釋放了全局鎖之後,纔能有其餘的線程來執行。也就是說,CPython自己其實是一個單線程語言,甚至在多核CPU上面使用CPython的多線程反而性能不如單線程高。
人們對於GIL還存在很大的誤解,GIL只存在於Python中的CPython,使用Jython或者PyPy則不存在這個問題。
- Python的線程是操做系統線程。在Linux上爲pthread,在Windows上爲Win thread,徹底由操做系統調度線程的執行。一個python解釋器進程內有一條主線程,以及多條用戶程序的執行線程。即便在多核CPU平臺上,因爲GIL的存在,因此禁止多線程的並行執行。
- Python解釋器進程內的多線程是合做多任務方式執行。當一個線程遇到I/O任務時,將釋放GIL。計算密集型(CPU-bound)的線程在執行大約100次解釋器的計步(ticks)時,將釋放GIL。計步(ticks)可粗略看做Python虛擬機的指令。計步實際上與時間片長度無關。能夠經過sys.setcheckinterval()設置計步長度。
- 在單核CPU上,數百次的間隔檢查纔會致使一次線程切換。在多核CPU上,存在嚴重的線程顛簸(thrashing)。
咱們都知道計算機一開始只是單核的,在那個年代人們並不會想到多核這種狀況,因而爲了應對多線程的數據同步問題,人們發明了鎖。可是若是本身來寫一個鎖,不只耗時耗力,並且還會隱藏許多未知的BUG等問題。因而在這樣的大背景下,Python社區選擇了最簡單粗暴的方式,實現GIL,這樣作有如下幾點好處:
- 能夠增長單線程程序的運行速度(再也不須要對全部數據結構分別獲取或釋放鎖)
- 容易和大部分非線程安全的 C 庫進行集成
- 容易實現(使用單獨的 GIL 鎖要比實現無鎖,或者細粒度鎖的解釋器更容易)
可是令Python社區沒想到的是,CPU乃至計算機發展的如此迅速,雙核,四核,甚至多CPU計算機的出現,讓Python在很長一段時間內揹負着運行效率低下的稱號。而當Python社區和衆多的Python庫做者回過頭想修改這些問題的時候卻發現,代碼與代碼之間緊緊的依賴於GIL,面對龐大的繞成一團的線,也只能抽絲剝繭般的慢慢剔除。
值得慶幸的是,雖然咱們不知道這一過程用了多久,可是在Python3.2中開始使用了全新的GIL,將大大的提高CPython的性能。
不少人提到Python就想到爬蟲,由於爬蟲在某些程度上來講,Python的缺點徹底不存在,並且還成了優勢。咱們來分析一下爬蟲的運行過程:
- 發送請求
- 等待服務器響應
- 收到服務器響應數據
- 解析
咱們來看一下,以當前的計算機配置來講,對爬蟲獲取到的數據來進行解析處理的話,可能只須要幾毫秒甚至更短的時間就能完成。那麼一個爬蟲程序最影響性能的地方在哪裏?
是IO操做。沒錯,咱們的爬蟲發出請求之後要等待對方的服務器來響應,這一過程是最耗時的,有時可能會須要一兩秒的時間。此時,咱們就能夠在請求發送出去之後,馬上釋放咱們的全局鎖,而後讓下一個線程執行。直到某一個線程的響應回來之後消耗幾毫秒處理數據,而後再次開始發送請求,而因爲同一時間只有一條線程運行不須要考慮其餘的問題,因此性能也會大大的提高。
及時在爬蟲上使用了真正意義的多線程,無非就是在解析數據的時候多幾個線程來處理罷了。那麼0.2毫秒和0.02毫秒,乃至無限至今於0毫秒的時間,他們之間的區別又是什麼呢?一樣都是人類沒法分辨出來的差距,而咱們又要對線程進行大量的安全性的處理,得不償失。
在瞭解了線程之後,咱們可能須要在多個線程之間通訊。實現這一點,咱們能夠聲明一個全局的存儲對象,全部的線程都調用這一個對象來進行數據的存和取,這樣就能夠作到線程間的通訊。
咱們使用傳統的列表或元組都是能夠的,可是列表和元組他們都是線程不安全的存儲結構,咱們須要本身加鎖來保證線程安全。或者咱們能夠直接使用Python內置的線程安全的存儲結構,Queue。
Queue的使用以下:
# Python2.x from Queue import Queue # Python3.x import queue # Python2.x q = Queue() # Python3.x q = queue.Queue() # 存儲一個元組到Queue中 q.put((1, 'a')) # q.get()每次獲取一個數據,使用下面這種方式能夠直接拆分元組 int_data, str_data = q.get()
注意:Queue每次獲取數據之後都會把獲取的數據刪除從內部,因此不用擔憂獲取到重複的數據。使用q.queue屬性,能夠獲得裏面全部的數據,返回的是一個deque對象。
有時咱們但願讓某一個線程進入等待狀態來進行一些其餘的處理,當咱們某些事情處理完成之後,再喚醒線程,讓它從剛纔中止的地方繼續運行。使用標準庫下面的Threading.Event就能夠實現。
休眠事件:wait()
喚醒事件:set()
清除事件:clear()
from threading import Event, Thread # 接收一個Event對象 def test_event(e): print('run...') # 讓這個線程進入睡眠狀態 e.wait() # 當線程被喚醒之後,會輸出下面的語句 print('end...') e = Event() t = Thread(target=test_event, args=(e,)) # 這裏會看到輸出了 run... t.start() print('Set Event...') # 喚醒線程會看到 end... e.set()
上面程序最終運行結果爲:
run...
Set Event...
end...
注意:當咱們程序在一塊兒運行週期內,重複調用e.wait(),第二次調用就沒法讓線程進入休眠狀態了,須要調用e.clear()清除之後,才能再次進入休眠狀態。
from threading import Event, Thread def test_event(e): print('run...') e.wait() # 爲了重複使用,須要加上e.clear() # e.clear() print('end...') e = Event() t = Thread(target=test_event, args=(e,)) t.start() # 第一次成功休眠 print('Set Event1...') e.set() t = Thread(target=test_event, args=(e,)) t.start() # 第二次休眠失敗 print('Set Event2...') e.set()
不去掉e.clear()的註釋,根據線程的切換順序,可能獲得各類輸出結果,能夠本身屢次嘗試看看有什麼不一樣的結果。
去掉e.clear()的註釋之後,輸出結果以下:
run...
Set Event1...
end...
run...
Set Event2...
end...
在多線程環境中,咱們有多個繼承了Thread的類,他們之間相互調用。假設咱們此時有一個MyThread的類,它是爲其餘線程服務的。如今,其餘線程的全部操做已經所有完成了,而咱們的MyThread的run方法裏面有一個死循環,咱們怎麼在其餘線程都完成工做,中止了之後,中止咱們的MyThread類中的死循環?
from threading import Thread class MyThread(Thread): def run(self): while True: # 控制其餘各個線程的代碼 pass def __init__(self): Thread.__init__(self) # 設置守護線程 self.setDaemon(True)
在__init__
中,self實際上就是Thread類的對象,因此setDaemon其實是Thread類的一個方法,當設置爲True就能夠把當前類變成一個守護線程,等到其餘線程都中止之後,它會自動中止。
有些場景下,咱們但願每一個線程,都有本身獨立的數據,他們使用同一個變量,可是在每一個線程內的數據都是獨立的互不干擾的。
咱們可使用threading.local()來實現:
import threading L = threading.local() L.num = 1 # 此時操做的是咱們當前主線程的threading.local()對象,輸出結果爲1 print(L.num) def f(): print(L.num) # 建立一個子線程,去調用f(),看可否訪問主線程中定義的L.num t = threading.Thread(target=f) t.start() # 結果提示咱們: # AttributeError: '_thread._local' object has no attribute 'num'
對上面的稍做修改:
import threading L = threading.local() L.num = 1 # 此時操做的是咱們當前主線程的threading.local()對象,輸出結果爲1 print(L.num) def f(): L.num = 5 # 這裏能夠成功的輸出5 print(L.num) # 建立一個子線程,去調用f(),看可否訪問主線程中定義的L.num t = threading.Thread(target=f) t.start() # 主線程中的L.num依然是1,沒有發生任何改變 print(L.num)
程序運行結果爲:
1
5
1
因而可知,threading.local()建立的對象中的屬性,是對於每一個線程獨立存在的,它們相互之間沒法干擾,咱們稱它爲線程本地數據。
https://www.imooc.com/article/16198
python threading.current_thread().name和.getName()有什麼區別 今天學到python多線程這塊,想顯示當前線程是主線程仍是子線程.網上一搜,有個方法叫 1 threading.current().name 可是發現,一樣的threading.current_thread()後面不只僅有.name屬性,並且還有.getName()方法.可是 這2個寫法最後得出的結果倒是同樣的. 那麼,2者區別在哪裏呢? 1 import threading 2 import time 3 4 def run(arg): 5 print("running sub thread...{}".format(threading.current_thread())) 6 time.sleep(3) 7 8 if __name__ == "__main__": 9 t1 = threading.Thread(target=run,args=("t1",)) 10 t1.start() 11 print("mian Thread...{}".format(threading.current_thread().getName())) 12 print("mian Thread...{}".format(threading.current_thread().name)) 13 14 t2 = threading.Thread() name 是當前線程的屬性, getName 是當前線程的方法。 儘管 threading.current_thread().name 和 threading.current_thread().getName() 的結果同樣,可是徹底不是同一種東西呀, 例如經過 threading.current_thread().name = 'thread_python' 來改變它。
最終演示代碼: 1 import threading 2 import time 3 4 def run(arg): 5 print("running sub thread...{}".format(threading.current_thread())) 6 threading.current_thread().name="xurui_python" 7 print("sub1 Thread...{}".format(threading.current_thread().getName())) 8 print("sub2 Thread...{}".format(threading.current_thread().name)) 9 time.sleep(3) 10 11 if __name__ == "__main__": 12 t1 = threading.Thread(target=run,args=("t1",)) 13 t1.start() 14 print("mian1 Thread...{}".format(threading.current_thread().getName())) 15 print("mian2 Thread...{}".format(threading.current_thread().name)) 代碼結果: 1 running sub thread...<Thread(Thread-1, started 23296)> 2 mian1 Thread...MainThread 3 mian2 Thread...MainThread 4 sub1 Thread...xurui_python 5 sub2 Thread...xurui_python
test