Python線程編程之Thread詳解
一、線程編程(Thread)
1、線程基本概念
1.1、什么事線程
- 線程被稱為輕量級(jí)的進(jìn)程
- 線程也可以使用計(jì)算機(jī)多核資源,是多任務(wù)編程方式
- 線程是系統(tǒng)分配內(nèi)核的最小單元
- 線程可以理解為進(jìn)程的分支任務(wù)
1.2、線程特征
- 一個(gè)進(jìn)程中可以包含多個(gè)線程
- 線程也是一個(gè)運(yùn)行行為,消耗計(jì)算機(jī)資源
- 一個(gè)線程中的所有線程共享這個(gè)進(jìn)程的資源
- 多個(gè)線程之間的運(yùn)行互不影響各自運(yùn)行
- 線程的創(chuàng)建和銷毀消耗資源遠(yuǎn)小于進(jìn)程
- 各個(gè)線程也有自己的ID等特征

二、threading模塊創(chuàng)建線程
1、創(chuàng)建線程對(duì)象
from threading import Thread t = Thread() 功能: 創(chuàng)建線程對(duì)象 參數(shù): target 綁定線程函數(shù) args 元組 給線程函數(shù)位置傳參 kwargs 字典 給線程函數(shù)鍵值傳參
2、 啟動(dòng)線程
t.start()
3、 回收線程
t.join([timeout])
4、代碼演示
"""
thread1.py 線程基礎(chǔ)使用
步驟:
1. 封裝線程函數(shù)
2.創(chuàng)建線程對(duì)象
3.啟動(dòng)線程
4.回收線程
"""
import os
from threading import Thread
from time import sleep
a = 1
# 線程函數(shù)
def music():
for i in range(3):
sleep(2)
print('播放:黃河大合唱 %s' % os.getpid())
global a
print("a,",a)
a = 1000
# 創(chuàng)建線程對(duì)象
t = Thread(target=music)
# 啟動(dòng)線程
t.start()
for i in range(3):
sleep(1)
print('播放:beauty love %s' % os.getpid())
# 回收線程
t.join()
print('程序結(jié)束')
print("a,", a)
5、線程對(duì)象屬性
1.t.name 線程名稱
2.t.setName() 設(shè)置線程名稱
3.t.getName()獲取線程名稱
4.t.is_alive() 查看線程是否在生命周期
5.t.daemon 設(shè)置主線程和分支線程退出分支線程也退出.要在start前設(shè)置 通常不和join 一起使用
6.代碼演示
"""
thread3.py
線程屬性演示
"""
from threading import Thread
from time import sleep
def fun():
sleep(3)
print('線程屬性測(cè)試')
t = Thread(target=fun, name='ceshi')
# 主線程退出分支線程也退出 必須在start前使用 與join 沒有意義
t.setDaemon(True)
t.start()
print(t.getName())
t.setName('Tedu')
print('is alive:', t.is_alive())
print('daemon', t.daemon)
6、自定義線程類
1.創(chuàng)建步驟
1.繼承Thread類
2.重寫 __init__方法添加自己的屬性 使用super加載父類屬性
3.重寫run方法
2.使用方法
1.實(shí)例化對(duì)象
2.調(diào)傭start自動(dòng)執(zhí)行run方法
3.調(diào)傭join回收線程
代碼演示
"""
自定義線程類例子
"""
from threading import Thread
# 自定義線程類
class ThreadClass(Thread):
# 重寫父類 init
def __init__(self, *args, **kwargs):
self.attr = args[0]
# 加載父類init
super().__init__()
# 假設(shè)需要很多步驟完成功能
def f1(self):
print('1')
def f2(self):
print(2)
# 重寫run 邏輯調(diào)傭
def run(self):
self.f1()
self.f2()
t = ThreadClass()
t.start()
t.join()
7、一個(gè)很重要的練習(xí) 我很多不懂
from threading import Thread
from time import sleep, ctime
class MyThread(Thread):
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None):
super().__init__()
self.fun = target
self.args = args
self.kwargs = kwargs
def run(self):
self.fun(*self.args, **self.kwargs)
def player(sec, song):
for i in range(3):
print("Playing %s : %s" % (song, ctime()))
sleep(sec)
t = MyThread(target=player, args=(3,), kwargs={'song': '量量'})
t.start()
t.join()
8、線程間通信
1.通信方法
1.線程間使用全局遍歷進(jìn)行通信
2.共享資源爭(zhēng)奪
1.共享資源:多個(gè)進(jìn)程或者線程都可以操作的資源稱為共享資源,對(duì)共享資源的操作代碼段稱為臨界區(qū)
2.影響:對(duì)公共資源的無序操作可能會(huì)帶來數(shù)據(jù)的混亂,或者操作錯(cuò)誤.此時(shí)往往需要同步互斥機(jī)制協(xié)調(diào)操作順序
3.同步互斥機(jī)制
1.同步:同步是一種協(xié)作關(guān)系,為完成操作,多進(jìn)程或者線程形成一種協(xié)調(diào),按照必要的步驟有序執(zhí)行操作
![?[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-CDK1Js97-1639582357964)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211215223825231.png)]](http://img.jbzj.com/file_images/article/202112/20211216161312121.png)
? 2.互斥:互斥是一種制約關(guān)系,當(dāng)一個(gè)進(jìn)程或者線程占有資源時(shí),會(huì)進(jìn)行加鎖處理,此時(shí)其它進(jìn)程線程就無法操作該資源,直到解鎖后才能操作

## 9.線程同步互斥方法
1. 線程Event 代碼演示
from threading import Event # 創(chuàng)建線程event對(duì)象 e = Event() # 阻塞等待e被set e.wait([timeout]) # 設(shè)置e, 使wait結(jié)束阻塞 e.set() # 使e回到未被設(shè)置狀態(tài) e.clear() # 查看當(dāng)前e是否被設(shè)置 e.is_set()
"""
event 線程互斥方法演示
"""
from threading import Event, Thread
s = None # 用于通信
e = Event()
def yzr():
print('楊子榮前來拜山頭')
global s
s = '天王蓋地虎'
e.set() #操作完共享資源 e設(shè)置
t = Thread(target=yzr)
t.start()
print('說對(duì)口令就是自己人')
e.wait() #阻塞等待 e.set()
if s == '天王蓋地虎':
print('寶塔鎮(zhèn)河妖')
print('確認(rèn)過眼神,你是對(duì)的人')
e.clear()
else:
print('打死他...')
t.join()
print('程序結(jié)束')
2. 線程鎖 Lock代碼演示
from threading import Lock lock = Lock()創(chuàng)建鎖對(duì)象 lock.acquire() 上鎖 如果lock已經(jīng)上鎖再調(diào)用會(huì)阻塞 lock.release() 解鎖 with lock: 上鎖 .... .... with 代碼塊解鎖自動(dòng)解鎖
"""
thread_lock
線程鎖演示
"""
from threading import Thread, Lock
a = b = 0
lock = Lock()
def value():
while True:
# 上鎖
lock.acquire()
print('a=%d,b=%d' % (a, b)) if a != b else print('a不等于b')
# 解鎖
lock.release()
t = Thread(target=value)
t.start()
while True:
# with 開始上鎖
with lock:
a += 1
b += 1
# with 解鎖 自動(dòng)解鎖
t.join()
print('程序結(jié)束')
10、死鎖及其處理
1.定義
死鎖是指兩個(gè)或者兩個(gè)以上的線程在執(zhí)行過程中,由于競(jìng)爭(zhēng)資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,他們都將無法推進(jìn)下去.此時(shí)稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖.
2.圖解

3. 死鎖產(chǎn)生條件
死鎖發(fā)生的必要條件
- 互斥條件:指線程對(duì)所分配到的資源進(jìn)行排它性使用,即在一段時(shí)間內(nèi)某資源只由一個(gè)進(jìn)程占用。如果此時(shí)還有其它進(jìn)程請(qǐng)求資源,則請(qǐng)求者只能等待,直至占有資源的進(jìn)程用畢釋放。
- 請(qǐng)求和保持條件:指線程已經(jīng)保持至少一個(gè)資源,但又提出了新的資源請(qǐng)求,而該資源已被其它進(jìn)程占有,此時(shí)請(qǐng)求線程阻塞,但又對(duì)自己已獲得的其它資源保持不放。
- 不剝奪條件:指線程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時(shí)由自己釋放,通常CPU內(nèi)存資源是可以被系統(tǒng)強(qiáng)行調(diào)配剝奪的。
- 環(huán)路等待條件:指在發(fā)生死鎖時(shí),必然存在一個(gè)線程——資源的環(huán)形鏈,即進(jìn)程集合{T0,T1,T2,···,Tn}中的T0正在等待一個(gè)T1占用的資源;T1正在等待T2占用的資源,……,Tn正在等待已被T0占用的資源。
- 死鎖的產(chǎn)生原因
簡(jiǎn)單來說造成死鎖的原因可以概括成三句話:
- 當(dāng)前線程擁有其他線程需要的資源
- 當(dāng)前線程等待其他線程已擁有的資源
- 都不放棄自己擁有的資源
如何避免死鎖
死鎖是我們非常不愿意看到的一種現(xiàn)象,我們要盡可能避免死鎖的情況發(fā)生。通過設(shè)置某些限制條件,去破壞產(chǎn)生死鎖的四個(gè)必要條件中的一個(gè)或者幾個(gè),來預(yù)防發(fā)生死鎖。預(yù)防死鎖是一種較易實(shí)現(xiàn)的方法。但是由于所施加的限制條件往往太嚴(yán)格,可能會(huì)導(dǎo)致系統(tǒng)資源利用率。
4.死鎖代碼演示
from time import sleep
from threading import Thread, Lock
# 交易類
class Account:
def __init__(self, _id, balance, lock):
# 用戶
self._id = _id
# 存款
self.balance = balance
# 鎖
self.lock = lock
# 取錢
def withdraw(self, amount):
self.balance -= amount
# 存錢
def deposit(self, amount):
self.balance += amount
# 余額
def get_balance(self):
return self.balance
Tom = Account('Tom', 5000, Lock())
Alex = Account('Alex', 8000, Lock())
def transfer(from_, to, amount):
# 鎖住自己賬戶
if from_.lock.acquire():
# 賬戶減少
from_.withdraw(amount)
sleep(0.5)
if to.lock.acquire():
to.deposit(amount)
to.lock.release()
from_.lock.release()
print('轉(zhuǎn)賬完成 %s給%s轉(zhuǎn)賬%d' % (from_._id, to._id, amount))
# transfer(Tom, Alex, 1000)
t1 = Thread(target=transfer, args=(Tom, Alex, 2000))
t2 = Thread(target=transfer, args=(Alex, Tom, 3500))
t1.start()
t2.start()
t1.join()
t2.join()
print('程序結(jié)束')
python線程GIL
1.python線程的GIL問題 (全局解釋器鎖)
什么是GIL :由于python解釋器設(shè)計(jì)中加入了解釋器鎖,導(dǎo)致python解釋器同一時(shí)刻只能解釋執(zhí)行一個(gè)線程,大大降低了線程的執(zhí)行效率。
導(dǎo)致后果: 因?yàn)橛龅阶枞麜r(shí)線程會(huì)主動(dòng)讓出解釋器,去解釋其他線程。所以python多線程在執(zhí)行多阻塞高延遲IO時(shí)可以提升程序效率,其他情況并不能對(duì)效率有所提升。
GIL問題建議
- 盡量使用進(jìn)程完成無阻塞的并發(fā)行為
- 不使用c作為解釋器 (Java C#)
總結(jié):
在無阻塞狀態(tài)下,多線程程序和單線程程序執(zhí)行效率幾乎差不多,甚至還不如單線程效率。但是多進(jìn)程運(yùn)行相同內(nèi)容卻可以有明顯的效率提升。
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
PyTorch中l(wèi)oading fbgemm.dll異常的解決辦法
PyTorch是一個(gè)深度學(xué)習(xí)框架,當(dāng)我們?cè)诒镜卣{(diào)試大模型時(shí),可能會(huì)選用并安裝它,目前已更新至2.4版本,本文給大家介紹了PyTorch中l(wèi)oading fbgemm.dll異常的解決辦法,文中通過代碼和圖文介紹的非常詳細(xì),需要的朋友可以參考下2024-08-08
pymysql實(shí)現(xiàn)增刪改查的操作指南(python)
python中可以使用pymysql來MySQL數(shù)據(jù)庫的連接,并實(shí)現(xiàn)數(shù)據(jù)庫的各種操作,這篇文章主要給大家介紹了關(guān)于pymsql實(shí)現(xiàn)增刪改查的相關(guān)資料,需要的朋友可以參考下2021-05-05
解決Ubuntu18中的pycharm不能調(diào)用tensorflow-gpu的問題
這篇文章主要介紹了解決Ubuntu18中的pycharm不能調(diào)用tensorflow-gpu的問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
python和numpy?matplotlib版本匹配及安裝指定版本庫
Matplotlib 是 Python 的繪圖庫,它經(jīng)常與NumPy一起使用,從而提供一種能夠代替Matlab的方案,這篇文章主要給大家介紹了關(guān)于python和numpy?matplotlib版本匹配及安裝指定版本庫的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10
python實(shí)現(xiàn)識(shí)別手寫數(shù)字 python圖像識(shí)別算法
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)識(shí)別手寫數(shù)字,python圖像識(shí)別算法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01

