Python多線程即相關理念詳解
一、什麼是線程?
線程顧名思義,就是一條流水線工作的過程,一條流水線必須屬於一個車間,一個車間的工作過程是一個進程。車間負責把資源整合到一起,是一個資源單位,而一個車間內至少有一個流水線。所以,進程隻是用來把資源集中到一起(進程隻是一個資源單位,或者說資源集合),而線程才是cpu上的執行單位。
總結進程與線程區別:
''' 進程:資源單位 線程:執行單位 線程才是真正幹活的人,幹活中需要的資源由線程所在進程提供 每個進程肯定自帶一個線程 每個進程內可創建多個線程 ''' ''' 開進程: 申請空間 拷貝代碼 消耗資源大 開線程: 同一個進程內創建多個線程,無需上述兩種操作,消耗資源相對較小 '''
多線程(即多個控制線程)的概念是,在一個進程中存在多個控制線程,多個控制線程共享該進程的地址空間,相當於一個車間內有多條流水線,都共用一個車間的資源。
二、開啟線程的兩種方式
1、方式1
from threading import Thread import time # 方法一 def task(name): print('%s is running' % name) time.sleep(1) print('%s is over' % name) # 開啟線程不需要在main下面執行代碼,直接書寫就可以 # 但是習慣性的將啟動命令寫在main下面 t = Thread(target=task, args=('egon',)) t.start() # 創建線程的開銷非常小,幾乎是代碼一執行就已經創建瞭 print('主') '''
運行結果:
egon is running
主
egon is over
”’
2、方式2
from threading import Thread class MyThread(Thread): def __init__(self, name): # 重寫瞭別人的方法,又不知道別人的方法裡有啥,就調用父類的方法 super().__init__() self.name = name def run(self): print('%s is running' % self.name) time.sleep(1) print('%s is over' % self.name) if __name__ == '__main__': t = MyThread('egon') t.start() print('主') '''
運行結果:
egon is running
主
egon is over
”’
三、線程對象的jion方法()
看過我講解進程文章的小夥伴想必都知道jion的功能,線程的jion方法於進程的jion方法功能類似-等待一個線程執行完畢後再執行下一個線程
from threading import Thread def task(name): print('%s is running' % name) time.sleep(1) print('%s is over' % name) if __name__ == '__main__': t=Thread(target=task,args=('egon',)) t.start() t.join()# 主線程等待子線程運行結束後再執行 print('主')
”’
運行結果:
egon is running
egon is over
主
”’
補充一個知識點:同一個進程下的多個線程數據共享,下面為大傢舉一個簡單的案例
from threading import Thread money=100 def task(): global money money=66 if __name__ == '__main__': t=Thread(target=task,args=()) t.start() print(money)
# 結果:66
四、 補充小案例
from threading import Thread import os,time def task(): print('子 pid:',os.getpid()) if __name__ == '__main__': t=Thread(target=task,args=()) t.start() print('主 pid:',os.getpid()) # 兩個線程的pid號一樣,說明在同一個進程下
”’
運行結果:
子 pid: 13444
主 pid: 13444
”’
# 這是個容易混淆的案例 from threading import Thread,current_thread,active_count import os,time def task(n): print('子',current_thread().name) time.sleep(n) # 延長線程存活時間 if __name__ == '__main__': t=Thread(target=task,args=(1,)) t1=Thread(target=task,args=(1,)) t.start() t1.start() t.join() # print('主',current_thread().name)# 獲取線程名字 print(active_count()) # 統計當前活躍的進程數
”’
運行結果:
子 Thread-1
子 Thread-2
1
”’
# 這裡大傢容易以為是3,其實運行後隻有一個線程在活躍瞭,其它兩個線程運行完後就停止運行瞭
五、守護線程
守護線程與守護進程的概念也類似,其實大傢也能註意到,進程與線程有許多知識點即用法都是相通的,理解瞭一個另一個也是差不多的道理
1、守護線程會隨著主線程的結束而結束
2、主線程運行結束後不會立刻結束,會等待所有的其它非守護線程結束後才會結束
3、因為主線程的結束意味著所在進程的結束
from threading import Thread import time def task(name): print('%s is running'%name) time.sleep(1) print('%s is over'%name) if __name__ == '__main__': t=Thread(target=task,args=('egon',)) t.daemon=True #將t設置為守護線程 t.start() print('主')
”’
運行結果:
egon is running
主
”’
# 稍微有點迷惑性的例子 from threading import Thread import time def foo(): print('1234') time.sleep(1) print('end1234') def func(): print('5678') time.sleep(3) print('end5678') if __name__ == '__main__': t1=Thread(target=foo,args=()) t2=Thread(target=func,args=()) t1.daemon=True # t1設為守護線程,t2為非守護線程 t1.start() t2.start() print('主......')
”’
運行結果:
1234
5678主……
end1234
end5678
”’”’
因主線程會等待非守護線程運行結束後在結束,
所有主線程會等待t2(非守護線程)結束再結束,
”’
六、線程互斥鎖
多個線程操作同一份數據的時候,會出現數據錯亂的問題
針對上述問題,解決方式就是加鎖處理
from threading import Thread,Lock import time money=100 mutex=Lock() def task(): global money mutex.acquire() tmp=money time.sleep(0.1)# 模擬網絡延遲 money=tmp-1 mutex.release() if __name__ == '__main__': t_list=[] for i in range(100): t=Thread(target=task,args=()) t.start() t_list.append(t) for t in t_list: t.join() print(money)
# 運行結果:0
# 多個人操作同一份數據,數據錯亂,加鎖處理
七、GTL-全局解釋器
相信學python的小夥伴都知道,python解釋器其實有多個版本
- Cpython
- Jpython
- Pypython
但是普遍使用的都是Cpython解釋器
在Cpython解釋器中GIL是一把互斥鎖,用來阻止同一個進程下的多個線程的同時執行
要註意同一進程下的多個線程無法利用多核優勢!!!!
想必大傢心中也有不少疑惑:pyhon的多線程是不是一點用都沒瞭呢????
因為Cpython中的內存管理不是線程安全的。多線程並不是一無是處的,在遇到多IO操作的時候,多核的優勢也會顯示不出來,多進程與多線程的效率在該情況下差不瞭多少,而此時多進程相對浪費資源,多線程更加節省資源
ps:內存管理就是垃圾回收機制:
1、引用計數
2、標記清除
3、分帶回收
# GTL-全局解釋器 # 重點:1、GIL不是python的特點而是Cpython解釋器的特點 # 2、GIL是保證解釋器級別的數據的安全 # 3、GIL會導致同一個進程下的多個線程無法同時進行(即無法利用多核優勢) # 4、針對不同的數據還是需要加不同的鎖處理 # 5、解釋型語言的通病,同一個進程下多個線程無法利用多核優勢
多線程是否有用要看具體情況
八、驗證多線程與多線程運用場景
# 計算密集型(CPU一直工作,也沒有IO)(更適合多進程) from multiprocessing import Process from threading import Thread import os,time # 多進程情況 def work(): res=0 for i in range(0,10000000): res*=i if __name__ == '__main__': l=[] print(os.cpu_count())# 獲取當前計算機CPU核數 start_time=time.time() for i in range(8):# 我計算機是8核 p= Process(target=work,args=()) p.start() l.append(p) for p in l: p.join() print(time.time()-start_time)
”’
運行結果:
8
2.0726492404937744
”’
# 多線程情況 from multiprocessing import Process from threading import Thread import os,time def work(): res=0 for i in range(0,10000000): res*=i if __name__ == '__main__': l=[] print(os.cpu_count())# 獲取當前計算機CPU核數 start_time=time.time() for i in range(8):# 我計算機是8核 t=Thread(target=work,args=()) t.start() l.append(t) for p in l: p.join() print(time.time()-start_time)
”’
運行結果:
8
3.5790603160858154
”’# 顯然可知:計算密集型更時候多進程
# IO密集型(任務一直有IO)(多線程更合適) from multiprocessing import Process from threading import Thread import os,time # 多線程 def work(): time.sleep(1) if __name__ == '__main__': l=[] start_time=time.time() for i in range(40): t=Thread(target=work,args=()) t.start() l.append(t) for p in l: p.join() print(time.time()-start_time) # 運行結果:1.0205152034759521 # 多進程 from multiprocessing import Process from threading import Thread import os,time def work(): time.sleep(1) if __name__ == '__main__': l=[] start_time=time.time() for i in range(40): p= Process(target=work,args=()) # t=Thread(target=work,args=()) # t.start() # l.append(t) p.start() l.append(p) for p in l: p.join() print(time.time()-start_time)
# 運行結果:5.927189588546753
# 顯然可知:IO密集型更適合多線程
總結:
多線程和多進程都各自有各自的優勢
並且在後面的項目中通常可以多進程下面再開設多線程
這樣的話我們可以利用多核也可以節省資源消耗
本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!
推薦閱讀:
- Python解析器Cpython的GIL解釋器鎖工作機制
- Python學習筆記之線程
- python threading模塊的使用指南
- Python多線程以及多線程中join()的使用方法示例
- python 多線程實現多任務的方法示例