1.多任務
在計算機中,操作系統(tǒng)可以同時運行多個任務,這就是多任務熊镣。那么如何解決多個任務同時運行呢,那就需要用到多線程募书。多任務可以通過并發(fā)和并行來完成绪囱,那么什么是并發(fā)和并行呢?
? 1.并發(fā):指的是任務數(shù)多于cpu核數(shù)莹捡,通過操作系統(tǒng)的各種任務調(diào)度算法鬼吵,實現(xiàn)用多個任務“一起”執(zhí)行(實際上總有一些任務不在執(zhí)行,因為切換任務的速度相當快篮赢,看上去一起執(zhí)行而已)齿椅,在實際開發(fā)中,并發(fā)是最常用的启泣。
? 2.并行:當任務數(shù)小于或者等于cpu核數(shù)時涣脚,每一個任務都有對應的cpu來處理執(zhí)行,即任務真的是一起執(zhí)行的寥茫。
2.多任務的實現(xiàn)-多線程
實現(xiàn)多任務需要用到多線程遣蚀,那么什么是線程呢?下面是對它的理解:
????1.一個程序運行起來至少有一個進程纱耻,一個進程至少有一個線程
????2.處理器cpu分配給線程芭梯,即cpu真正運行的是線程中的代碼
????3.分配cpu給線程時,是通過時間片輪訓方式進行的
????4.進程是操作系統(tǒng)分配程序執(zhí)行資源的單位弄喘,而線程是進程的一個實體粥帚,
是CPU調(diào)度和分配的單位。
實現(xiàn)多線程的方式有2種方式:
1.python的thread模塊是比較底層的模塊限次,python的threading模塊是對thread做了一些包裝的,可以更加方便的被使用,通過threading模塊可以創(chuàng)建線程
# 引入threading線程模塊
import????threading卖漫,time?
def????download_music():
'''模擬下載歌曲费尽,需要5秒鐘下載完成'''
????for? ? i? ? in? ? range(5):
?????time.sleep(1)# 休眠1秒
????print("---正在下載歌曲%d---"% i)
def????main():
????# 創(chuàng)建線程對象t1 ,target: 指向新開啟的線程要執(zhí)行的代碼
????t1 = threading.Thread(target=download_music)?
? ? t1.start()# 啟動線程,即線程開始執(zhí)行
if__name__ =='__main__':?
?????main()
2.繼承Thread類羊始,創(chuàng)建一個新的class旱幼,將要執(zhí)行的代碼 寫到run函數(shù)里面
import????threading,time
# 自定義類突委,繼承threading.Thread
class????MyThread(threading.Thread):
????def????run(self):
????????for????i????in????range(5):
?????????????time.sleep(1)
????????????# name屬性中保存的是當前線程的名字
? ? ? ? ? ? msg ="I'm "+ self.name +' @ '+ str(i)?
?????????????print(msg)
if__name__ =='__main__':
# 通過MyThread創(chuàng)建線程對象
????t1 = MyThread()
????# 開始執(zhí)行線程
????t1.start()
3.多線程的注意點
1. 線程何時開啟柏卤,何時結(jié)束
1.子線程何時開啟,何時運行
? 當調(diào)用thread.start()時 開啟線程匀油,再運行線程的代碼
2.子線程何時結(jié)束
? 子線程把target指向的函數(shù)中的語句執(zhí)行完畢后缘缚,或者線程中的run函數(shù)代碼執(zhí)行完畢后,立即結(jié)束當前子線程
3.查看當前線程數(shù)量
? 通過threading.enumerate()可枚舉當前運行的所有線程
4.主線程何時結(jié)束
? 所有子線程執(zhí)行完畢后敌蚜,主線程才結(jié)束
2. 線程的執(zhí)行順序
多線程的創(chuàng)建與執(zhí)行都是無序的桥滨。
3.總結(jié)
1.每個線程默認有一個名字,盡管上面的例子中沒有指定線程對象的name弛车,但是python會自動為線程指定一個名字齐媒。
2.當線程的run()方法結(jié)束時該線程完成。
3.無法控制線程調(diào)度程序纷跛,但可以通過別的方式來影響線程調(diào)度的方式喻括。
4.共享全局變量
先來看一段代碼:
from????threading????import????Thread
import????time
g_nums = [11,22,33]
def? ? main():
????def????work1(nums):
????????nums.append(44)?
?????????print("----in work1---",nums)
????def????work2(nums):
????????#延時一會,保證t1線程中的事情做完
????????time.sleep(1)?
?????????print("----in work2---",nums)
if? ? __name__ == '__main__':
????t1 = Thread(target=work1, args=(g_nums,))
????t1.start()
????t2 = Thread(target=work2, args=(g_nums,))
????t2.start()
運行結(jié)果:
----inwork1--- [11,22,33,44]
----inwork2--- [11,22,33,44]
總結(jié):
在一個進程內(nèi)的所有線程共享全局變量贫奠,很方便在多個線程間共享數(shù)據(jù)
缺點就是唬血,多線程對全局變量隨意遂改可能造成全局變量的混亂(即線程非安全)
5.多線程開發(fā)可能遇到的問題
????假設兩個線程t1和t2都要對全局變量g_num(默認是0)進行加1運算,t1和t2都各對g_num加10次叮阅,g_num的最終的結(jié)果應該為20刁品。
????但是由于是多線程同時操作,有可能出現(xiàn)下面情況:
????????在g_num=0時浩姥,t1取得g_num=0挑随。此時系統(tǒng)把t1調(diào)度為”sleeping”狀態(tài),把t2轉(zhuǎn)換為”running”狀態(tài)勒叠,t2也獲得g_num=0
????????然后t2對得到的值進行加1并賦給g_num兜挨,使得g_num=1
????????然后系統(tǒng)又把t2調(diào)度為”sleeping”,把t1轉(zhuǎn)為”running”眯分。線程t1又把它之前得到的0加1后賦值給g_num拌汇。
????????這樣導致雖然t1和t2都對g_num加1,但結(jié)果仍然是g_num=1弊决。
????所以噪舀,如果多個線程同時對同一個全局變量操作魁淳,會出現(xiàn)資源競爭問題,從而數(shù)據(jù)結(jié)果會不正確与倡,即會遇到線程安全問題
6.使用同步機制解決線程安全問題-互斥鎖
同步就是協(xié)同步調(diào)界逛,按預定的先后次序進行運行。如:你說完纺座,我再說息拜。
"同"字從字面上容易理解為一起動作,其實不是净响,"同"字應是指協(xié)同少欺、協(xié)助、互相配合馋贤。
如進程赞别、線程同步,可理解為進程或線程A和B一塊配合掸掸,A執(zhí)行到一定程度時要依靠B的某個結(jié)果氯庆,于是停下來,示意B運行;B執(zhí)行扰付,再將結(jié)果給A;A再繼續(xù)操作堤撵。
在多線程編程里面,一些敏感數(shù)據(jù)不允許被多個線程同時訪問羽莺,此時就使用同步訪問技術(shù)实昨,保證數(shù)據(jù)在任何時刻,最多有一個線程訪問盐固,以保證數(shù)據(jù)的正確性荒给。
互斥鎖:
????當多個線程幾乎同時修改某一個共享數(shù)據(jù)的時候,需要進行同步控制
????線程同步能夠保證多個線程安全訪問競爭資源刁卜,最簡單的同步機制是引入互斥鎖志电。
????互斥鎖為資源引入一個狀態(tài):鎖定/非鎖定
????某個線程要更改共享數(shù)據(jù)時,先將其鎖定蛔趴,此時資源的狀態(tài)為“鎖定”挑辆,其他線程不能更改;直到該線程釋放資源孝情,將資源的狀態(tài)變成“非鎖定”鱼蝉,其他的線程才能再次鎖定該資源◇锏矗互斥鎖保證了每次只有一個線程進行寫入操作魁亦,從而保證了多線程情況下數(shù)據(jù)的正確性。
threading模塊中定義了Lock類羔挡,可以方便的處理鎖定:
# 1.創(chuàng)建鎖
mutex = threading.Lock()
#2. 鎖定
mutex.acquire()
#3. 釋放
mutex.release()
注意:
如果這個鎖之前是沒有上鎖的洁奈,那么acquire不會堵塞
如果在調(diào)用acquire對這個鎖上鎖之前 它已經(jīng)被 其他線程上了鎖间唉,那么此時acquire會堵塞,直到這個鎖被解鎖為止
示例:使用互斥鎖完成2個線程對同一個全局變量各加100萬次的操作
import????threading睬魂,time
g_num =0
def????test1(num):
????global????g_num
????for????i????in????range(num):
? ? ? ? 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)建一個互斥鎖终吼,默認是未上鎖的狀態(tài)
mutex = threading.Lock()
# 創(chuàng)建2個線程,讓他們各自對g_num加1000000次
p1 = threading.Thread(target=test1, args=(1000000,))
p1.start()
p2 = threading.Thread(target=test2, args=(1000000,))
p2.start()
# 等待計算完成
while????len(threading.enumerate()) !=1:?
?????time.sleep(1)
print("2個線程對同一個全局變量操作之后的最終結(jié)果是:%s"% g_num)
運行結(jié)果:
---test1---g_num=1909909
---test2---g_num=2000000
2個線程對同一個全局變量操作之后的最終結(jié)果是:2000000
可以看到最后的結(jié)果氯哮,加入互斥鎖后,其結(jié)果與預期相符商佛。
上鎖解鎖過程:
????當一個線程調(diào)用鎖的acquire()方法獲得鎖時喉钢,鎖就進入“l(fā)ocked”狀態(tài)。
????每次只有一個線程可以獲得鎖良姆。如果此時另一個線程試圖獲得這個鎖肠虽,該線程就會變?yōu)椤癰locked”狀態(tài),稱為“阻塞”玛追,直到擁有鎖的線程調(diào)用鎖的release()方法釋放鎖之后税课,鎖進入“unlocked”狀態(tài)。
????線程調(diào)度程序從處于同步阻塞狀態(tài)的線程中選擇一個來獲得鎖痊剖,并使得該線程進入運行(running)狀態(tài)韩玩。
7.死鎖
在線程間共享多個資源的時候,如果兩個線程分別占有一部分資源并且同時等待對方的資源陆馁,就會造成死鎖找颓。
盡管死鎖很少發(fā)生,但一旦發(fā)生就會造成應用的停止響應叮贩。下面看一個死鎖的例子
import????threading 击狮,time? ?
printer_mutex = threading.Lock()# 打印機鎖
paper_mutext = threading.Lock()# 紙張鎖
class????ResumeThread(threading.Thread):
"""編寫個人簡歷任務的線程"""
????def????run(self):
????????print("ResumeThread:編寫個人簡歷任務")
????????# 使用打印機資源,先對打印機加鎖
????????printer_mutex.acquire()?
?????????print("--ResumeThread:正在使用打印機資源--")?
?????????time.sleep(1)# 休眠1秒
????????# 使用紙張耗材益老,先對紙張耗材加鎖
????????paper_mutext.acquire()?
????????print("--正在使用紙張資源--")?
?????????time.sleep(1)?
?????????paper_mutext.release()# 釋放紙張鎖
????????# 釋放打印機鎖
????????printer_mutex.release()
class????PaperListThread(threading.Thread):
"""盤點紙張耗材任務的線程"""
????def????run(self):
????????print("PaperListThread:盤點紙張耗材任務")
????????# 使用紙張耗材彪蓬,先對紙張耗材加鎖
????????paper_mutext.acquire()?
?????????print("--PaperListThread:正在盤點紙張耗材--")
?????????time.sleep(1)# 休眠1秒
????????# 使用打印機資源,打印清單
????????printer_mutex.acquire()
?????????print("--正在使用打印機資源--")
?????????time.sleep(1)?
?????????printer_mutex.release()# 釋放打印機鎖
????????# 釋放紙張耗材鎖
????????paper_mutext.release()
if__name__ =='__main__':
? ? ?t1 = ResumeThread()
?????t2 = PaperListThread()
?????t1.start()?
?????t2.start()
從運行結(jié)果可以看出捺萌,兩個線程都在等待對方釋放資源档冬,造成了死鎖。
避免死鎖的方式:
????1.程序設計時要盡量避免
? ? 2.添加超時時間等