python的thread模塊是比較底層的模塊,python的threading模塊是對(duì)thread做了一些包裝的邮辽,可以更加方便的被使用
查看線程數(shù)量
#coding=utf-8
import threading
from time import sleep,ctime
def sing():
for i in range(3):
print("正在唱歌...%d"%i)
sleep(1)
def dance():
for i in range(3):
print("正在跳舞...%d"%i)
sleep(1)
if __name__ == '__main__':
print('---開(kāi)始---:%s'%ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
while True:
length = len(threading.enumerate())
print('當(dāng)前運(yùn)行的線程數(shù)為:%d'%length)
if length<=1:
break
sleep(0.5)
結(jié)果
---開(kāi)始---:Thu Dec 27 11:22:19 2018
正在唱歌...0
正在跳舞...0
當(dāng)前運(yùn)行的線程數(shù)為:3
當(dāng)前運(yùn)行的線程數(shù)為:3
正在唱歌...1
正在跳舞...1
當(dāng)前運(yùn)行的線程數(shù)為:3
當(dāng)前運(yùn)行的線程數(shù)為:3
正在唱歌...2
正在跳舞...2
當(dāng)前運(yùn)行的線程數(shù)為:3
當(dāng)前運(yùn)行的線程數(shù)為:3
當(dāng)前運(yùn)行的線程數(shù)為:1
當(dāng)調(diào)用Thread的時(shí)候不會(huì)創(chuàng)建線程只是創(chuàng)建了一個(gè)對(duì)象,只有在調(diào)用start的時(shí)候才會(huì)創(chuàng)建
通過(guò)使用threading模塊能完成多任務(wù)的程序開(kāi)發(fā)扮宠,為了讓每個(gè)線程的封裝性更完美滥沫,所以使用threading模塊時(shí),往往會(huì)定義一個(gè)新的子類class允跑,只要繼承threading.Thread就可以了王凑,然后重寫run方法,這里調(diào)用start其實(shí)就是調(diào)用了Thread的run方法聋丝。
- python的threading.Thread類有一個(gè)run方法索烹,用于定義線程的功能函數(shù),可以在自己的線程類中覆蓋該方法弱睦。而創(chuàng)建自己的線程實(shí)例后百姓,通過(guò)Thread類的start方法,可以啟動(dòng)該線程况木,交給python虛擬機(jī)進(jìn)行調(diào)度垒拢,當(dāng)該線程獲得執(zhí)行的機(jī)會(huì)時(shí),就會(huì)調(diào)用run方法執(zhí)行線程火惊。
#coding=utf-8
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
time.sleep(1)
msg = "I'm "+self.name+' @ '+str(i)
print(msg)
def test():
for i in range(5):
t = MyThread()
t.start()
if __name__ == '__main__':
test()
執(zhí)行結(jié)果:
I'm Thread-1 @ 0
I'm Thread-3 @ 0
I'm Thread-2 @ 0
I'm Thread-4 @ 0
I'm Thread-5 @ 0
I'm Thread-1 @ 1
I'm Thread-3 @ 1
I'm Thread-2 @ 1
I'm Thread-4 @ 1
I'm Thread-5 @ 1
I'm Thread-1 @ 2
I'm Thread-2 @ 2
I'm Thread-3 @ 2
I'm Thread-4 @ 2
I'm Thread-5 @ 2
從代碼和執(zhí)行結(jié)果我們可以看出求类,多線程程序的執(zhí)行順序是不確定的。當(dāng)執(zhí)行到sleep語(yǔ)句時(shí)屹耐,線程將被阻塞(Blocked)尸疆,到sleep結(jié)束后,線程進(jìn)入就緒(Runnable)狀態(tài)惶岭,等待調(diào)度寿弱。而線程調(diào)度將自行選擇一個(gè)線程執(zhí)行。上面的代碼中只能保證每個(gè)線程都運(yùn)行完整個(gè)run函數(shù)按灶,但是線程的啟動(dòng)順序症革、run函數(shù)中每次循環(huán)的執(zhí)行順序都不能確定。
總結(jié)
1鸯旁、每個(gè)線程默認(rèn)有一個(gè)名字噪矛,盡管上面的例子中沒(méi)有指定線程對(duì)象的name,但是python會(huì)自動(dòng)為線程指定一個(gè)名字羡亩。
2摩疑、當(dāng)線程的run()方法結(jié)束時(shí)該線程完成。
3畏铆、無(wú)法控制線程調(diào)度程序雷袋,但可以通過(guò)別的方式來(lái)影響線程調(diào)度的方式
互斥鎖
當(dāng)多個(gè)線程幾乎同時(shí)修改某一個(gè)共享數(shù)據(jù)的時(shí)候,需要進(jìn)行同步控制。線程同步能夠保證多個(gè)線程安全訪問(wèn)競(jìng)爭(zhēng)資源楷怒,最簡(jiǎn)單的同步機(jī)制是引入互斥鎖蛋勺。互斥鎖為資源引入一個(gè)狀態(tài):鎖定/非鎖定
某個(gè)線程要更改共享數(shù)據(jù)時(shí)鸠删,先將其鎖定抱完,此時(shí)資源的狀態(tài)為“鎖定”,其他線程不能更改刃泡;直到該線程釋放資源巧娱,將資源的狀態(tài)變成“非鎖定”,其他的線程才能再次鎖定該資源烘贴〗恚互斥鎖保證了每次只有一個(gè)線程進(jìn)行寫入操作,從而保證了多線程情況下數(shù)據(jù)的正確性桨踪。
threading模塊中定義了Lock類老翘,可以方便的處理鎖定:
# 創(chuàng)建鎖
mutex = threading.Lock()
# 鎖定
mutex.acquire()
# 釋放
mutex.release()
注意:
如果這個(gè)鎖之前是沒(méi)有上鎖的,那么acquire不會(huì)堵塞
如果在調(diào)用acquire對(duì)這個(gè)鎖上鎖之前 它已經(jīng)被 其他線程上了鎖锻离,那么此時(shí)acquire會(huì)堵塞铺峭,直到這個(gè)鎖被解鎖為止
import threading
import time
g_num = 0
def test1(num):
global g_num
for i in range(num):
#如果之前沒(méi)被上鎖,那么上鎖成功
#如果之前已經(jīng)被上鎖了汽纠,那么會(huì)堵塞在這里卫键,直到這個(gè)鎖被解開(kāi)
mutex.acquire() # 上鎖
g_num += 1
mutex.release() # 解鎖
print("---test1---g_num=%d"%g_num)
def test2(num):
global g_num
for i in range(num):
mutex.acquire() # 上鎖
g_num += 1
mutex.release() # 解鎖
print("---test2---g_num=%d"%g_num)
# 創(chuàng)建一個(gè)互斥鎖
# 默認(rèn)是未上鎖的狀態(tài)
mutex = threading.Lock()
# 創(chuàng)建2個(gè)線程,讓他們各自對(duì)g_num加1000000次
p1 = threading.Thread(target=test1, args=(1000000,))
p1.start()
p2 = threading.Thread(target=test2, args=(1000000,))
p2.start()
# 等待計(jì)算完成
while len(threading.enumerate()) != 1:
time.sleep(1)
print("2個(gè)線程對(duì)同一個(gè)全局變量操作之后的最終結(jié)果是:%s" % g_num)
運(yùn)行結(jié)果
---test1---g_num=1000000
---test2---g_num=2000000
2個(gè)線程對(duì)同一個(gè)全局變量操作之后的最終結(jié)果是:2000000
第一個(gè)線程上鎖直到執(zhí)行完之后才到第二個(gè)線程繼續(xù)執(zhí)行疏虫。 上鎖的位置不同永罚,產(chǎn)生的效果也會(huì)不同啤呼,如果將子線程對(duì)數(shù)據(jù)操作的代碼進(jìn)行上鎖卧秘,則兩個(gè)線程將交叉對(duì)數(shù)據(jù)進(jìn)行操作,修改示例如下
for i in range(num):
mutex.acquire() # 上鎖
g_num += 1
mutex.release() # 解鎖
運(yùn)行結(jié)果:
---test2---g_num=1768428
---test1---g_num=2000000
2個(gè)線程對(duì)同一個(gè)全局變量操作之后的最終結(jié)果是:2000000
上鎖解鎖過(guò)程
當(dāng)一個(gè)線程調(diào)用鎖的acquire()方法獲得鎖時(shí)官扣,鎖就進(jìn)入“l(fā)ocked”狀態(tài)翅敌。
每次只有一個(gè)線程可以獲得鎖。如果此時(shí)另一個(gè)線程試圖獲得這個(gè)鎖惕蹄,該線程就會(huì)變?yōu)椤癰locked”狀態(tài)蚯涮,稱為“阻塞”,直到擁有鎖的線程調(diào)用鎖的release()方法釋放鎖之后卖陵,鎖進(jìn)入“unlocked”狀態(tài)遭顶。
線程調(diào)度程序從處于同步阻塞狀態(tài)的線程中選擇一個(gè)來(lái)獲得鎖,并使得該線程進(jìn)入運(yùn)行(running)狀態(tài)泪蔫。
總結(jié)
鎖的好處:
- 確保了某段關(guān)鍵代碼只能由一個(gè)線程從頭到尾完整地執(zhí)行
鎖的壞處:
- 阻止了多線程并發(fā)執(zhí)行棒旗,包含鎖的某段代碼實(shí)際上只能以單線程模式執(zhí)行,效率就大大地下降了
- 由于可以存在多個(gè)鎖撩荣,不同的線程持有不同的鎖铣揉,并試圖獲取對(duì)方持有的鎖時(shí)饶深,可能會(huì)造成死鎖