多任務(wù)-線程

1.多任務(wù)

在計(jì)算機(jī)中,操作系統(tǒng)可以同時(shí)運(yùn)行多個(gè)任務(wù),這就是多任務(wù)。那么如何解決多個(gè)任務(wù)同時(shí)運(yùn)行呢,那就需要用到多線程。多任務(wù)可以通過并發(fā)和并行來完成,那么什么是并發(fā)和并行呢?

? 1.并發(fā):指的是任務(wù)數(shù)多于cpu核數(shù),通過操作系統(tǒng)的各種任務(wù)調(diào)度算法,實(shí)現(xiàn)用多個(gè)任務(wù)“一起”執(zhí)行(實(shí)際上總有一些任務(wù)不在執(zhí)行,因?yàn)榍袚Q任務(wù)的速度相當(dāng)快,看上去一起執(zhí)行而已),在實(shí)際開發(fā)中,并發(fā)是最常用的。

? 2.并行:當(dāng)任務(wù)數(shù)小于或者等于cpu核數(shù)時(shí),每一個(gè)任務(wù)都有對應(yīng)的cpu來處理執(zhí)行,即任務(wù)真的是一起執(zhí)行的。

2.多任務(wù)的實(shí)現(xiàn)-多線程

實(shí)現(xiàn)多任務(wù)需要用到多線程,那么什么是線程呢?下面是對它的理解:

????1.一個(gè)程序運(yùn)行起來至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程

????2.處理器cpu分配給線程,即cpu真正運(yùn)行的是線程中的代碼

????3.分配cpu給線程時(shí),是通過時(shí)間片輪訓(xùn)方式進(jìn)行的

????4.進(jìn)程是操作系統(tǒng)分配程序執(zhí)行資源的單位,而線程是進(jìn)程的一個(gè)實(shí)體,

是CPU調(diào)度和分配的單位。

實(shí)現(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)建一個(gè)新的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屬性中保存的是當(dāng)前線程的名字

? ? ? ? ? ? msg ="I'm "+ self.name +' @ '+ str(i)?

?????????????print(msg)

if__name__ =='__main__':

# 通過MyThread創(chuàng)建線程對象

????t1 = MyThread()

????# 開始執(zhí)行線程

????t1.start()

3.多線程的注意點(diǎn)

1. 線程何時(shí)開啟,何時(shí)結(jié)束

1.子線程何時(shí)開啟,何時(shí)運(yùn)行

? 當(dāng)調(diào)用thread.start()時(shí) 開啟線程,再運(yùn)行線程的代碼

2.子線程何時(shí)結(jié)束

? 子線程把target指向的函數(shù)中的語句執(zhí)行完畢后,或者線程中的run函數(shù)代碼執(zhí)行完畢后,立即結(jié)束當(dāng)前子線程

3.查看當(dāng)前線程數(shù)量

? 通過threading.enumerate()可枚舉當(dāng)前運(yùn)行的所有線程

4.主線程何時(shí)結(jié)束

? 所有子線程執(zhí)行完畢后,主線程才結(jié)束

2. 線程的執(zhí)行順序

多線程的創(chuàng)建與執(zhí)行都是無序的。

3.總結(jié)

1.每個(gè)線程默認(rèn)有一個(gè)名字,盡管上面的例子中沒有指定線程對象的name,但是python會自動為線程指定一個(gè)名字。

2.當(dāng)線程的run()方法結(jié)束時(shí)該線程完成。

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):

????????#延時(shí)一會,保證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()

運(yùn)行結(jié)果:

----inwork1--- [11,22,33,44]

----inwork2--- [11,22,33,44]

總結(jié):

在一個(gè)進(jìn)程內(nèi)的所有線程共享全局變量,很方便在多個(gè)線程間共享數(shù)據(jù)

缺點(diǎn)就是,多線程對全局變量隨意遂改可能造成全局變量的混亂(即線程非安全)

5.多線程開發(fā)可能遇到的問題

????假設(shè)兩個(gè)線程t1和t2都要對全局變量g_num(默認(rèn)是0)進(jìn)行加1運(yùn)算,t1和t2都各對g_num加10次,g_num的最終的結(jié)果應(yīng)該為20。

????但是由于是多線程同時(shí)操作,有可能出現(xiàn)下面情況:

????????在g_num=0時(shí),t1取得g_num=0。此時(shí)系統(tǒng)把t1調(diào)度為”sleeping”狀態(tài),把t2轉(zhuǎn)換為”running”狀態(tài),t2也獲得g_num=0

????????然后t2對得到的值進(jìn)行加1并賦給g_num,使得g_num=1

????????然后系統(tǒng)又把t2調(diào)度為”sleeping”,把t1轉(zhuǎn)為”running”。線程t1又把它之前得到的0加1后賦值給g_num。

????????這樣導(dǎo)致雖然t1和t2都對g_num加1,但結(jié)果仍然是g_num=1。

????所以,如果多個(gè)線程同時(shí)對同一個(gè)全局變量操作,會出現(xiàn)資源競爭問題,從而數(shù)據(jù)結(jié)果會不正確,即會遇到線程安全問題

6.使用同步機(jī)制解決線程安全問題-互斥鎖

同步就是協(xié)同步調(diào),按預(yù)定的先后次序進(jìn)行運(yùn)行。如:你說完,我再說。

"同"字從字面上容易理解為一起動作,其實(shí)不是,"同"字應(yīng)是指協(xié)同、協(xié)助、互相配合。

如進(jìn)程、線程同步,可理解為進(jìn)程或線程A和B一塊配合,A執(zhí)行到一定程度時(shí)要依靠B的某個(gè)結(jié)果,于是停下來,示意B運(yùn)行;B執(zhí)行,再將結(jié)果給A;A再繼續(xù)操作。

在多線程編程里面,一些敏感數(shù)據(jù)不允許被多個(gè)線程同時(shí)訪問,此時(shí)就使用同步訪問技術(shù),保證數(shù)據(jù)在任何時(shí)刻,最多有一個(gè)線程訪問,以保證數(shù)據(jù)的正確性。

互斥鎖:

????當(dāng)多個(gè)線程幾乎同時(shí)修改某一個(gè)共享數(shù)據(jù)的時(shí)候,需要進(jìn)行同步控制

????線程同步能夠保證多個(gè)線程安全訪問競爭資源,最簡單的同步機(jī)制是引入互斥鎖。

????互斥鎖為資源引入一個(gè)狀態(tài):鎖定/非鎖定

????某個(gè)線程要更改共享數(shù)據(jù)時(shí),先將其鎖定,此時(shí)資源的狀態(tài)為“鎖定”,其他線程不能更改;直到該線程釋放資源,將資源的狀態(tài)變成“非鎖定”,其他的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個(gè)線程進(jìn)行寫入操作,從而保證了多線程情況下數(shù)據(jù)的正確性。

threading模塊中定義了Lock類,可以方便的處理鎖定:

# 1.創(chuàng)建鎖

mutex = threading.Lock()

#2. 鎖定

mutex.acquire()

#3. 釋放

mutex.release()

注意:

如果這個(gè)鎖之前是沒有上鎖的,那么acquire不會堵塞

如果在調(diào)用acquire對這個(gè)鎖上鎖之前 它已經(jīng)被 其他線程上了鎖,那么此時(shí)acquire會堵塞,直到這個(gè)鎖被解鎖為止

示例:使用互斥鎖完成2個(gè)線程對同一個(gè)全局變量各加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)建一個(gè)互斥鎖,默認(rèn)是未上鎖的狀態(tài)

mutex = threading.Lock()

# 創(chuàng)建2個(gè)線程,讓他們各自對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è)線程對同一個(gè)全局變量操作之后的最終結(jié)果是:%s"% g_num)

運(yùn)行結(jié)果:

---test1---g_num=1909909

---test2---g_num=2000000

2個(gè)線程對同一個(gè)全局變量操作之后的最終結(jié)果是:2000000

可以看到最后的結(jié)果,加入互斥鎖后,其結(jié)果與預(yù)期相符。

上鎖解鎖過程:

????當(dāng)一個(gè)線程調(diào)用鎖的acquire()方法獲得鎖時(shí),鎖就進(jìn)入“l(fā)ocked”狀態(tài)。

????每次只有一個(gè)線程可以獲得鎖。如果此時(shí)另一個(gè)線程試圖獲得這個(gè)鎖,該線程就會變?yōu)椤癰locked”狀態(tài),稱為“阻塞”,直到擁有鎖的線程調(diào)用鎖的release()方法釋放鎖之后,鎖進(jìn)入“unlocked”狀態(tài)。

????線程調(diào)度程序從處于同步阻塞狀態(tài)的線程中選擇一個(gè)來獲得鎖,并使得該線程進(jìn)入運(yùn)行(running)狀態(tài)。

7.死鎖

在線程間共享多個(gè)資源的時(shí)候,如果兩個(gè)線程分別占有一部分資源并且同時(shí)等待對方的資源,就會造成死鎖。

盡管死鎖很少發(fā)生,但一旦發(fā)生就會造成應(yīng)用的停止響應(yīng)。下面看一個(gè)死鎖的例子

import????threading ,time? ?

printer_mutex = threading.Lock()# 打印機(jī)鎖

paper_mutext = threading.Lock()# 紙張鎖

class????ResumeThread(threading.Thread):

"""編寫個(gè)人簡歷任務(wù)的線程"""

????def????run(self):

????????print("ResumeThread:編寫個(gè)人簡歷任務(wù)")

????????# 使用打印機(jī)資源,先對打印機(jī)加鎖

????????printer_mutex.acquire()?

?????????print("--ResumeThread:正在使用打印機(jī)資源--")?

?????????time.sleep(1)# 休眠1秒

????????# 使用紙張耗材,先對紙張耗材加鎖

????????paper_mutext.acquire()?

????????print("--正在使用紙張資源--")?

?????????time.sleep(1)?

?????????paper_mutext.release()# 釋放紙張鎖

????????# 釋放打印機(jī)鎖

????????printer_mutex.release()

class????PaperListThread(threading.Thread):

"""盤點(diǎn)紙張耗材任務(wù)的線程"""

????def????run(self):

????????print("PaperListThread:盤點(diǎn)紙張耗材任務(wù)")

????????# 使用紙張耗材,先對紙張耗材加鎖

????????paper_mutext.acquire()?

?????????print("--PaperListThread:正在盤點(diǎn)紙張耗材--")

?????????time.sleep(1)# 休眠1秒

????????# 使用打印機(jī)資源,打印清單

????????printer_mutex.acquire()

?????????print("--正在使用打印機(jī)資源--")

?????????time.sleep(1)?

?????????printer_mutex.release()# 釋放打印機(jī)鎖

????????# 釋放紙張耗材鎖

????????paper_mutext.release()

if__name__ =='__main__':

? ? ?t1 = ResumeThread()

?????t2 = PaperListThread()

?????t1.start()?

?????t2.start()

從運(yùn)行結(jié)果可以看出,兩個(gè)線程都在等待對方釋放資源,造成了死鎖。

避免死鎖的方式:

????1.程序設(shè)計(jì)時(shí)要盡量避免

? ? 2.添加超時(shí)時(shí)間等


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.進(jìn)程和線程 隊(duì)列:1、進(jìn)程之間的通信: q = multiprocessing.Queue()2、...
    一只寫程序的猿閱讀 1,233評論 0 17
  • 線程 1.同步概念 1.多線程開發(fā)可能遇到的問題 同步不是一起的意思,是協(xié)同步調(diào) 假設(shè)兩個(gè)線程t1和t2都要對nu...
    TENG書閱讀 695評論 0 1
  • 一文讀懂Python多線程 1、線程和進(jìn)程 計(jì)算機(jī)的核心是CPU,它承擔(dān)了所有的計(jì)算任務(wù)。它就像一座工廠,時(shí)刻在運(yùn)...
    星丶雲(yún)閱讀 1,595評論 0 4
  • 多任務(wù)可以由多進(jìn)程完成,也可以由一個(gè)進(jìn)程內(nèi)的多線程完成。我們前面提到了進(jìn)程是由若干線程組成的,一個(gè)進(jìn)程至少有一個(gè)線...
    壁花燒年閱讀 882評論 0 0
  • 今天我所要講的是,有點(diǎn)明白為什么世界上總有千千萬萬的微信名或QQ名叫做“明天會更好”了,不信你去輸入,不上千也成百...
    754ecf587f5f閱讀 407評論 0 1

友情鏈接更多精彩內(nèi)容