Python多線程即相關(guān)理念詳解
一、什么是線程?
線程顧名思義,就是一條流水線工作的過程,一條流水線必須屬于一個車間,一個車間的工作過程是一個進程。車間負責(zé)把資源整合到一起,是一個資源單位,而一個車間內(nèi)至少有一個流水線。所以,進程只是用來把資源集中到一起(進程只是一個資源單位,或者說資源集合),而線程才是cpu上的執(zhí)行單位。
總結(jié)進程與線程區(qū)別:
'''
進程:資源單位
線程:執(zhí)行單位
線程才是真正干活的人,干活中需要的資源由線程所在進程提供
每個進程肯定自帶一個線程
每個進程內(nèi)可創(chuàng)建多個線程
'''
'''
開進程:
申請空間
拷貝代碼
消耗資源大
開線程:
同一個進程內(nèi)創(chuàng)建多個線程,無需上述兩種操作,消耗資源相對較小
'''
多線程(即多個控制線程)的概念是,在一個進程中存在多個控制線程,多個控制線程共享該進程的地址空間,相當(dāng)于一個車間內(nèi)有多條流水線,都共用一個車間的資源。
二、開啟線程的兩種方式
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下面執(zhí)行代碼,直接書寫就可以
# 但是習(xí)慣性的將啟動命令寫在main下面
t = Thread(target=task, args=('egon',))
t.start() # 創(chuàng)建線程的開銷非常小,幾乎是代碼一執(zhí)行就已經(jīng)創(chuàng)建了
print('主')
'''
運行結(jié)果:
egon is running
主
egon is over
'''
2、方式2
from threading import Thread
class MyThread(Thread):
def __init__(self, name):
# 重寫了別人的方法,又不知道別人的方法里有啥,就調(diào)用父類的方法
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('主')
'''
運行結(jié)果:
egon is running
主
egon is over
'''
三、線程對象的jion方法()
看過我講解進程文章的小伙伴想必都知道jion的功能,線程的jion方法于進程的jion方法功能類似-等待一個線程執(zhí)行完畢后再執(zhí)行下一個線程
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()# 主線程等待子線程運行結(jié)束后再執(zhí)行
print('主')
?'''
運行結(jié)果:
egon is running
egon is over
主
'''
補充一個知識點:同一個進程下的多個線程數(shù)據(jù)共享,下面為大家舉一個簡單的案例
from threading import Thread
money=100
def task():
global money
money=66
if __name__ == '__main__':
t=Thread(target=task,args=())
t.start()
print(money)
# 結(jié)果: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號一樣,說明在同一個進程下
'''
運行結(jié)果:
子 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()) # 統(tǒng)計當(dāng)前活躍的進程數(shù)
'''
運行結(jié)果:
子 Thread-1
子 Thread-2
1
'''
# 這里大家容易以為是3,其實運行后只有一個線程在活躍了,其它兩個線程運行完后就停止運行了
五、守護線程
守護線程與守護進程的概念也類似,其實大家也能注意到,進程與線程有許多知識點即用法都是相通的,理解了一個另一個也是差不多的道理
1、守護線程會隨著主線程的結(jié)束而結(jié)束
2、主線程運行結(jié)束后不會立刻結(jié)束,會等待所有的其它非守護線程結(jié)束后才會結(jié)束
3、因為主線程的結(jié)束意味著所在進程的結(jié)束
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設(shè)置為守護線程
t.start()
print('主')
'''
運行結(jié)果:
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設(shè)為守護線程,t2為非守護線程
t1.start()
t2.start()
print('主......')
'''
運行結(jié)果:
1234
5678主......
end1234
end5678
'''
'''
因主線程會等待非守護線程運行結(jié)束后在結(jié)束,
所有主線程會等待t2(非守護線程)結(jié)束再結(jié)束,
'''
六、線程互斥鎖
多個線程操作同一份數(shù)據(jù)的時候,會出現(xiàn)數(shù)據(jù)錯亂的問題
針對上述問題,解決方式就是加鎖處理
from threading import Thread,Lock
import time
money=100
mutex=Lock()
def task():
global money
mutex.acquire()
tmp=money
time.sleep(0.1)# 模擬網(wǎng)絡(luò)延遲
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)
# 運行結(jié)果:0
# 多個人操作同一份數(shù)據(jù),數(shù)據(jù)錯亂,加鎖處理
七、GTL-全局解釋器
相信學(xué)python的小伙伴都知道,python解釋器其實有多個版本
- Cpython
- Jpython
- Pypython
但是普遍使用的都是Cpython解釋器
在Cpython解釋器中GIL是一把互斥鎖,用來阻止同一個進程下的多個線程的同時執(zhí)行
要注意同一進程下的多個線程無法利用多核優(yōu)勢?。。?!
想必大家心中也有不少疑惑:pyhon的多線程是不是一點用都沒了呢????
因為Cpython中的內(nèi)存管理不是線程安全的。多線程并不是一無是處的,在遇到多IO操作的時候,多核的優(yōu)勢也會顯示不出來,多進程與多線程的效率在該情況下差不了多少,而此時多進程相對浪費資源,多線程更加節(jié)省資源
ps:內(nèi)存管理就是垃圾回收機制:
1、引用計數(shù)
2、標(biāo)記清除
3、分帶回收
# GTL-全局解釋器 # 重點:1、GIL不是python的特點而是Cpython解釋器的特點 # 2、GIL是保證解釋器級別的數(shù)據(jù)的安全 # 3、GIL會導(dǎo)致同一個進程下的多個線程無法同時進行(即無法利用多核優(yōu)勢) # 4、針對不同的數(shù)據(jù)還是需要加不同的鎖處理 # 5、解釋型語言的通病,同一個進程下多個線程無法利用多核優(yōu)勢
多線程是否有用要看具體情況
八、驗證多線程與多線程運用場景
# 計算密集型(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())# 獲取當(dāng)前計算機CPU核數(shù)
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)
'''
運行結(jié)果:
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())# 獲取當(dāng)前計算機CPU核數(shù)
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)
'''
運行結(jié)果:
8
3.5790603160858154
'''
# 顯然可知:計算密集型更時候多進程
# IO密集型(任務(wù)一直有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)
# 運行結(jié)果: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)
# 運行結(jié)果:5.927189588546753
# 顯然可知:IO密集型更適合多線程
總結(jié):
多線程和多進程都各自有各自的優(yōu)勢
并且在后面的項目中通??梢远噙M程下面再開設(shè)多線程
這樣的話我們可以利用多核也可以節(jié)省資源消耗
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Python 異步協(xié)程函數(shù)原理及實例詳解
這篇文章主要介紹了Python 異步協(xié)程函數(shù)原理及實例詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-11-11
python Tkinter版學(xué)生管理系統(tǒng)
這篇文章主要為大家詳細介紹了python Tkinter版學(xué)生管理系統(tǒng),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-02-02
python字符串分割常用方法(str.split()和正則)
在Python中字符串是一種非常常見的數(shù)據(jù)類型,在實際應(yīng)用中我們經(jīng)常需要對字符串進行分割,以便對其中的內(nèi)容進行處理,這篇文章主要給大家介紹了關(guān)于python字符串分割(str.split()和正則)的相關(guān)資料,需要的朋友可以參考下2023-11-11
如何安裝多版本python python2和python3共存以及pip共存
這篇文章主要為大家詳細介紹了python多版本的安裝方法,解決python2和python3共存以及pip共存問題,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-09-09
python 解決動態(tài)的定義變量名,并給其賦值的方法(大數(shù)據(jù)處理)
今天小編就為大家分享一篇python 解決動態(tài)的定義變量名,并給其賦值的方法(大數(shù)據(jù)處理),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-11-11
python利用有道翻譯實現(xiàn)"語言翻譯器"的功能實例
小編就為大家分享一篇python利用有道翻譯實現(xiàn)"語言翻譯器"的功能實例。具有比較好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-11-11

