Response——下載檔案、重定向
一:執行緒理論
一臺計算機相當於一個工廠,工廠裡有很多車間(程序),車間裡面有很多工人(執行緒)。
真正幹活兒的是工人(執行緒)。
1.什麼是執行緒
程序是資源分配的最小單位,而執行緒是程序中的一部分,每個程序中至少有一個執行緒,是CPU排程的最小單位。
程序是資源分配的最小單位,執行緒是CPU排程的最小單位
執行緒開銷更小,更輕量級。
2.執行緒的資料是共享的
在作業系統中,每一個程序的記憶體空間是獨立的,資料不共享。
但是同一個程序中的各個執行緒,資料是共享的。
二:開啟執行緒的2種方式
1.第一種 - 普通方式
from threading import Thread import time def task(t_name, s_time): print(f'子執行緒[{t_name}]開始') time.sleep(s_time) print(f'子執行緒[{t_name}]結束') if __name__ == '__main__': t1 = Thread(target=task, args=('t1', 1)) # 例項化得到一個執行緒物件 t1.start() # 物件.start()啟動執行緒 print('主執行緒') # 子執行緒[t1]開始 # 主執行緒 # 子執行緒[t1]結束
2.第二種 - 繼承類的方式
from threading import Thread
import time
class MyThread(Thread):
def run(self):
print('子執行緒開始')
time.sleep(1)
print('子執行緒結束')
if __name__ == '__main__':
t = MyThread()
t.start()
print('主執行緒')
# 子執行緒開始
# 主執行緒
# 子執行緒結束
三:TCP服務端實現併發效果
To be continue...
四:執行緒物件join方法
from threading import Thread import time def task(t_name, s_time): print(f'{t_name} 開始') time.sleep(s_time) print(f'{t_name} 結束') if __name__ == '__main__': t1 = Thread(target=task, args=('t1', 1)) t2 = Thread(target=task, args=('t2', 2)) t1.start() t2.start() t1.join() # 等待子執行緒執行結束 t2.join() print('主執行緒') # t1 開始 # t2 開始 # t1 結束 # t2 結束 # 主執行緒
五:同一個程序下的多個執行緒資料共享
from threading import Thread
import time
money = 99
def task(t_name, n):
global money
print(f'{t_name} 開始')
money = n
print(f'{t_name} 結束')
if __name__ == '__main__':
t1 = Thread(target=task, args=('t1', 10))
t2 = Thread(target=task, args=('t2', 100))
t1.start()
t2.start()
t1.join()
t2.join()
print(money)
print('主執行緒')
# t1 開始
# t1 結束
# t2 開始
# t2 結束
# 100
# 主執行緒
六:執行緒物件 及 其他方法
from threading import Thread, current_thread, active_count
import time
def task():
print('子執行緒開始')
print(current_thread().name) # 打印出當前執行緒名字
time.sleep(1)
print('子執行緒結束')
if __name__ == '__main__':
t1 = Thread(target=task, name='I\'m Thread1')
t2 = Thread(target=task, name='I\'m Thread2')
t1.start()
t2.start()
print(f'存活執行緒數:{active_count()}') # 打印出當前有多少存活的執行緒
# 子執行緒開始
# I'm Thread1
# 子執行緒開始
# I'm Thread2
# 存活執行緒數:3
# 子執行緒結束
# 子執行緒結束
from threading import Thread, current_thread, active_count
import time
import os
def task(t_name, n):
print(f'{t_name}開始')
print(f'當前執行緒:{current_thread().name}') # 執行緒名字
print(f'當前執行緒所在的程序ID:{os.getpid()}')
time.sleep(n)
print(f'{t_name}結束')
if __name__ == '__main__':
t1 = Thread(target=task, name='Thread1', args=('t1', 2))
t2 = Thread(target=task, name='Thread2', args=('t2', 5))
t1.start()
t2.start()
t1.join()
print(f'執行緒t1是否存活:{t1.is_alive()}')
print(f'執行緒t2是否存活:{t2.is_alive()}')
# 當作執行緒id號
print(f'執行緒t1的ID:{t1.ident}')
print(f'執行緒t2的ID:{t2.ident}')
print(f'當前執行緒所在的程序ID:{os.getpid()}')
print(f'存活執行緒數:{active_count()}') # 打印出2 ,存活的是t2和主執行緒
# t1開始
# 當前執行緒:Thread1
# 當前執行緒所在的程序ID:37420
# t2開始
# 當前執行緒:Thread2
# 當前執行緒所在的程序ID:37420
# t1結束
# 執行緒t1是否存活:False
# 執行緒t2是否存活:True
# 執行緒t1的ID:45360
# 執行緒t2的ID:73396
# 當前執行緒所在的程序ID:37420
# 存活執行緒數:2
# t2結束
知識點:
1.獲取執行緒名:t.name
、t.getName()
2.檢視當前程序下有幾個執行緒存活:active_count()
3.檢視當前執行緒是否存活:t1.is_alive()
4.檢視執行緒ID:t1.ident
5.檢視當前執行緒所在的程序ID:os.getpid()
七:守護執行緒
from threading import Thread, current_thread, active_count
import time
def task(t_name, n):
print(f'{t_name}開始')
time.sleep(n)
print(f'{t_name}結束')
if __name__ == '__main__':
t1 = Thread(target=task, name='Thread1', args=('Thread1', 10))
t2 = Thread(target=task, name='Thread2', args=('Thread2', 4))
t1.setDaemon(True)
t1.start()
t2.start()
print('主執行緒')
# Thread1開始
# Thread2開始
# 主執行緒
# Thread2結束
八:執行緒互斥鎖
from threading import Thread, Lock
import time
money = 99
def task(n, mutex):
global money
mutex.acquire() # 在修改資料的時候,加鎖
temp = money
# time.sleep(random.randint(1, 2))
time.sleep(0.1)
money = temp - 1
mutex.release() # 修改完之後,釋放鎖,其他執行緒就可以搶到鎖
if __name__ == '__main__':
ll = []
mutex = Lock()
for i in range(10):
t = Thread(target=task, args=(i, mutex))
t.start()
# t.join() # 不能在這裡join,會變成序列
ll.append(t)
for i in ll:
i.join()
print(money)
# 89
九:GIL 全域性直譯器鎖
1.什麼是GIL?
官網解釋:
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
在CPython中,全域性直譯器鎖(GIL)是一個互斥體,它阻止多個本機執行緒同時執行Python位元組碼。cpyth的執行緒管理不是必需的。(然而,由於GIL的存在,其他特性也越來越依賴於它所執行的保證。)
GIL(Global Interpreter Lock)全域性直譯器鎖
首先需要明確的一點是GIL並不是Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。
同樣一段程式碼可以通過CPython,PyPy,Psyco等不同的Python執行環境來執行,然而因為CPython是大部分環境下預設的Python執行環境。
所以在很多人的概念裡CPython就是Python,也就想當然的把GIL歸結為Python語言的缺陷,所有GIL並不是python的特性,僅僅是因為歷史原因在Cpython直譯器中難以移除。
2.GIL存在的原因
①CPython在執行多執行緒的時候並不是執行緒安全的,所以為了程式的穩定性,加一把全域性解釋鎖,能夠確保任何時候都只有一個Python執行緒執行。
②因為垃圾回收機制的存在,如果一個定義了一個x=2
,如果在一種較為極端的情況下,x
已經生成,但是2
還沒賦值給x
,此時的x
就會被垃圾回收機制當成垃圾 被回收,為了避免這種情況出現,加了一把全域性直譯器鎖。
3.執行緒釋放GIL鎖的2種情況
①.遇到I/O操作
例如:發出了HTTP請求,需要等待響應、讀寫檔案...
如果是
②Time Tick
到期
Time Tick規定了執行緒的最長執行時間,超過時間後自動釋放GIL鎖
3.單核與多核情況下的多執行緒任務排程
單核:
由此可以發現:由於GIL機制的存在,單核CPU在同一時刻,只有一個執行緒在執行。
當執行緒遇到了
I/O操作
或者Time Tick
到期的時候,就會釋放GIL鎖release GIL
。此時,其他2個執行緒去搶這把鎖,搶到之後,執行緒才能執行。
雖然出現I/O操作
和Time Tick
到期都會釋放GIL鎖,但是這2種情況是不一樣的:
出現I/O:
如果Thread1出現了IO操作,釋放了GIL鎖,Thread1會主動放棄搶鎖,不再參與這場競爭。
Time Tick到期:
如果Thread1出現了TimeTick到期,釋放了GIL鎖,Thread1會和其他執行緒競爭,同時搶這把GIL鎖
- 所以,在單核的情況下,CPU的利用率是很高的。
多核:
由此可以發現:Thread1在CPU1上執行,Thread2在CPU2上執行。
GIL是全域性的,CPU2上的Thread2需要等待CPU1上的Thread1讓出GIL鎖,才有可能執行。
如果在多次競爭中,Thread2都勝出,Thread1沒有得到GIL鎖,意味著CPU1一直是閒置的,無法發揮多核的優勢。
所以,有了GIL的存在,CPython的的多執行緒 其實就是 單執行緒。
4.針對與cpython直譯器,有哪些注意點?
①針對IO密集型:開多執行緒
②針對計算密集型:開多程序
5.既然GIL有缺陷,為什麼還有這麼多人用呢?
①.python的庫多,庫都是基於cpython寫起來的,其他直譯器沒有那麼多的第三方庫(海納百川,有容乃大
)。
②.雖然有這個歷史詬病,但是絲毫不影響我們敲程式碼(金無足赤,人無完人
)。
③.程式碼是一成不變的,但是我們可以利用它的優勢,優化它的劣勢(取其精華,去其糟粕
)
④.Python牛逼!(人生苦短,我用Python
)
6.總結
cpython直譯器中只有一個全域性鎖(GIL),執行緒必須獲取到GIL才能執行,我們開的多執行緒,不管有幾個CPU,同一時刻,只能有一個執行緒在執行(python的多執行緒,不能利用多核優勢)