Python 多線程詳解

      網(wǎng)友投稿 766 2022-05-29

      多線程詳解

      通過Thread創(chuàng)建多線程

      通過Thread子類創(chuàng)建多線程

      python的threading模塊是對thread做了一些包裝的,可以更加方便的被使用,線程的方法和進程的基本相似,這里就不多贅述,下面舉幾個栗子:

      #例一線程的基本用法 #coding=utf-8 import threading import time def xianyu(): print("咸魚普拉思") time.sleep(1) if __name__ == "__main__": for i in range(5): t = threading.Thread(target=xianyu) t.start() #啟動線程,即讓線程開始執(zhí)行 輸出: 咸魚普拉思 咸魚普拉思 咸魚普拉思 咸魚普拉思 咸魚普拉思 [Finished in 1.1s] #例二使用Threading子類創(chuàng)建多線程 #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() 輸出: I'm Thread-1 @ 0 I'm Thread-2 @ 0 I'm Thread-3 @ 0 I'm Thread-4 @ 0 I'm Thread-5 @ 0 I'm Thread-1 @ 1 I'm Thread-2 @ 1 I'm Thread-4 @ 1 I'm Thread-3 @ 1 I'm Thread-5 @ 1 I'm Thread-1 @ 2 I'm Thread-2 @ 2 I'm Thread-5 @ 2 I'm Thread-4 @ 2 I'm Thread-3 @ 2 [Finished in 3.2s]

      多進程是多份程序同時執(zhí)行

      多線程是在一份程序下多個執(zhí)行指針同時執(zhí)行

      多線程并不需要線程間通信,線程間共享全局變量,進程間不共享全局變量

      進程是系統(tǒng)進行資源分配和調(diào)度的一個獨立單位,線程是進程的一個實體,是CPU調(diào)度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本上不擁有系統(tǒng)資源,只擁有一點在運行中必不可少的資源(如程序計數(shù)器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。

      線程的劃分尺度小于進程(資源比進程少),使得多線程程序的并發(fā)性高。

      進程在執(zhí)行過程中擁有獨立的內(nèi)存單元,而多個線程共享內(nèi)存,從而極大地提高了程序的運行效率

      線線程不能夠獨立執(zhí)行,必須依存在進程中

      線程執(zhí)行開銷小,但不利于資源的管理和保護,而進程正相反

      線程在執(zhí)行過程中,如果中途執(zhí)行sleep語句時,線程會進入到阻塞狀態(tài),當sleep結(jié)束之后,線程進入就緒狀態(tài),等待調(diào)度而線程調(diào)度將自行選擇一個線程執(zhí)行 。

      具體線程狀態(tài)變換參照下圖:

      舉個栗子:

      from threading import Thread import time num = 100 def work1(): global num for i in range(3): num += 1 print("----in work1, num is %d---"%num) def work2(): global num print("----in work2, num is %d---"%num) print("---線程創(chuàng)建之前g_num is %d---"%num) t1 = Thread(target=work1) t1.start() #延時一會,保證t1線程中的事情做完 time.sleep(1) t2 = Thread(target=work2) t2.start() 輸出: ---線程創(chuàng)建之前g_num is 100--- ----in work1, num is 103--- ----in work2, num is 103--- [Finished in 1.1s]

      總結(jié):

      在一個進程內(nèi)的所有線程共享全局變量,能夠在不適用其他方式的前提下完成多線程之間的數(shù)據(jù)共享(這點要比多進程要好)

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

      舉個栗子:

      from threading import Thread import time num = 0 def test1(): global num for i in range(1000000): num += 1 print("---test1---num=%d"%g_numnum def test2(): global num for i in range(1000000): num += 1 print("---test2---num=%d"%num) p1 = Thread(target=test1) p1.start() # time.sleep(3) p2 = Thread(target=test2) p2.start() print("---num=%d---"%num) 輸出: 當time.sleep(3),沒有取消屏蔽時 ---num=235159--- ---test1---num=1172632 ---test2---num=1334237 [Finished in 0.3s] 當time.sleep(3),取消屏蔽時 ---test1---num=1000000 ---num=1014670--- ---test2---num=2000000 [Finished in 3.3s]

      上面舉的栗子就是線程不安全的現(xiàn)象,具體可以解釋為,線程1對數(shù)據(jù)num進行自增的時候,獲取的值是num=0,此時系統(tǒng)把線程1調(diào)度為”sleeping”狀態(tài) ,而線程2在做同樣操作時獲取的num值還是為0,同時做自增1的操作,這時在線程2中num的值為1,此時系統(tǒng)把線程2調(diào)度為”sleeping”狀態(tài),線程1再做自增操作時,num還是剛剛獲取到的0,長此往復下去,最終的結(jié)果就不是我們所預期的了。

      沒有控制多個線程對同一資源的訪問,對數(shù)據(jù)造成破壞,使得線程運行的結(jié)果不可預期 ,這種現(xiàn)象就是線程不安全。

      當多個線程幾乎同時修改某一個共享數(shù)據(jù)的時候,需要進行同步控制,線程同步能夠保證多個線程安全訪問競爭資源,最簡單的同步機制是引入互斥鎖。互斥鎖為資源引入一個狀態(tài):鎖定/非鎖定。

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

      舉個栗子:

      from threading import Thread, Lock import time num = 0 def test1(): global num for i in range(1000000): #True表示堵塞 即如果這個鎖在上鎖之前已經(jīng)被上鎖了,那么這個線程會在這里一直等待到解鎖為止 #False表示非堵塞,即不管本次調(diào)用能夠成功上鎖,都不會卡在這,而是繼續(xù)執(zhí)行下面的代碼 mutexFlag = mutex.acquire(True) if mutexFlag: num += 1 mutex.release() print("---test1---num=%d"%num) def test2(): global num for i in range(1000000): mutexFlag = mutex.acquire(True) #True表示堵塞 if mutexFlag: num += 1 mutex.release() print("---test2---num=%d"%num) #創(chuàng)建一個互斥鎖 #這個所默認是未上鎖的狀態(tài) mutex = Lock() p1 = Thread(target=test1) p1.start() p2 = Thread(target=test2) p2.start() print("---num=%d---"%num) 輸出: ---num=61866--- ---test1---num=1861180 ---test2---num=2000000

      當一個線程調(diào)用鎖的acquire()方法獲得鎖時,鎖就進入“l(fā)ocked”狀態(tài)。每次只有一個線程可以獲得鎖。如果此時另一個線程試圖獲得這個鎖,該線程就會變?yōu)椤癰locked”狀態(tài),稱為“阻塞”,直到擁有鎖的線程調(diào)用鎖的release()方法釋放鎖之后,鎖進入“unlocked”狀態(tài)。線程調(diào)度程序從處于同步阻塞狀態(tài)的線程中選擇一個來獲得鎖,并使得該線程進入運行狀態(tài)。

      加鎖確保了某段關(guān)鍵代碼只能由一個線程從頭到尾完整地執(zhí)行,但是阻止了多線程并發(fā)執(zhí)行,包含鎖的某段代碼實際上只能以單線程模式執(zhí)行,效率就大大地下降了,由于可以存在多個鎖,不同的線程持有不同的鎖,并試圖獲取對方持有的鎖時,可能會造成死鎖

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

      舉個栗子:

      #coding=utf-8 import threading import time class MyThread1(threading.Thread): def run(self): if mutexA.acquire(): print(self.name+'----do1---up----') time.sleep(1) if mutexB.acquire(): print(self.name+'----do1---down----') mutexB.release() mutexA.release() class MyThread2(threading.Thread): def run(self): if mutexB.acquire(): print(self.name+'----do2---up----') time.sleep(1) if mutexA.acquire(): print(self.name+'----do2---down----') mutexA.release() mutexB.release() mutexA = threading.Lock() mutexB = threading.Lock() if __name__ == '__main__': t1 = MyThread1() t2 = MyThread2() t1.start() t2.start()

      我們可以通過生產(chǎn)者和消費者模型來解決線程的同步,和線程安全。

      Python的Queue模塊中提供了同步的、線程安全的隊列類,包括FIFO(先入先出)隊列Queue,LIFO(后入先出)隊列LifoQueue,和優(yōu)先級隊列PriorityQueue。這些隊列都實現(xiàn)了鎖原語(可以理解為原子操作,即要么不做,要么就做完),能夠在多線程中直接使用,可以使用隊列來實現(xiàn)線程間的同步。

      舉個栗子:

      #encoding=utf-8 import threading import time #python2中 # from Queue import Queue #python3中 from queue import Queue class Producer(threading.Thread): def run(self): global queue count = 0 while True: if queue.qsize() < 1000: for i in range(100): count = count +1 msg = '生成產(chǎn)品'+str(count) queue.put(msg) print(msg) time.sleep(0.5) class Consumer(threading.Thread): def run(self): global queue while True: if queue.qsize() > 100: for i in range(3): msg = self.name + '消費了 '+queue.get() print(msg) time.sleep(1) if __name__ == '__main__': queue = Queue() for i in range(500): queue.put('初始產(chǎn)品'+str(i)) for i in range(2): p = Producer() p.start() for i in range(5): c = Consumer() c.start()

      生產(chǎn)者消費者模式是通過一個容器來解決生產(chǎn)者和消費者的強耦合問題。生產(chǎn)者和消費者彼此之間不直接通訊,而通過阻塞隊列來進行通訊,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后不用等待消費者處理,直接扔給阻塞隊列,消費者不找生產(chǎn)者要數(shù)據(jù),而是直接從阻塞隊列里取,阻塞隊列就相當于一個緩沖區(qū),平衡了生產(chǎn)者和消費者的處理能力。這個阻塞隊列就是用來給生產(chǎn)者和消費者解耦的。縱觀大多數(shù)設計模式,都會找一個第三者出來進行解耦。

      在多線程環(huán)境下,每個線程都有自己的數(shù)據(jù)。一個線程使用自己的局部變量比使用全局變量好,因為局部變量只有線程自己能看見,不會影響其他線程,而全局變量的修改必須加鎖。

      Python 多線程詳解

      舉個栗子:

      import threading # 創(chuàng)建全局ThreadLocal對象: local_school = threading.local() def process_student(): # 獲取當前線程關(guān)聯(lián)的student: std = local_school.student print('Hello, %s (in %s)' % (std, threading.current_thread().name)) def process_thread(name): # 綁定ThreadLocal的student: local_school.student = name process_student() t1 = threading.Thread(target= process_thread, args=('咸魚',), name='Thread-A') t2 = threading.Thread(target= process_thread, args=('普拉思',), name='Thread-B') t1.start() t2.start() t1.join() t2.join() 輸出: Hello, 咸魚 (in Thread-A) Hello, 普拉思 (in Thread-B)

      全局變量local_school就是一個ThreadLocal對象,每個Thread對它都可以讀寫student屬性,但互不影響。你可以把local_school看成全局變量,但每個屬性如local_school.student都是線程的局部變量,可以任意讀寫而互不干擾,也不用管理鎖的問題,ThreadLocal內(nèi)部會處理。

      可以理解為全局變量local_school是一個dict,不但可以用local_school.student,還可以綁定其他變量,如local_school.teacher等等。

      ThreadLocal最常用的地方就是為每個線程綁定一個數(shù)據(jù)庫連接,HTTP請求,用戶身份信息等,這樣一個線程的所有調(diào)用到的處理函數(shù)都可以非常方便地訪問這些資源。

      一個ThreadLocal變量雖然是全局變量,但每個線程都只能讀寫自己線程的獨立副本,互不干擾。ThreadLocal解決了參數(shù)在一個線程中各個函數(shù)之間互相傳遞的問題

      同步調(diào)用就是你喊你朋友吃飯,你朋友在忙,你就一直在那等,等你朋友忙完了 ,你們一起去。

      異步調(diào)用就是你喊你朋友吃飯,你朋友說知道了,待會忙完去找你 ,你就去做別的了。

      舉個栗子:

      from multiprocessing import Pool import time import os def test(): print("---進程池中的進程---pid=%d,ppid=%d--"%(os.getpid(),os.getppid())) for i in range(3): print("----%d---"%i) time.sleep(1) return "hahah" def test2(args): print("---callback func--pid=%d"%os.getpid()) print("---callback func--args=%s"%args) pool = Pool(3) pool.apply_async(func=test,callback=test2) time.sleep(5) print("----主進程-pid=%d----"%os.getpid()) 輸出: ---進程池中的進程---pid=9401,ppid=9400-- ----0--- ----1--- ----2--- ---callback func--pid=9400 ---callback func--args=hahah ----主進程-pid=9400----

      注意:這里的callback是由主進程執(zhí)行的,當子進程死亡,主進程回調(diào)函數(shù)。

      Python全局解釋鎖(GIL)簡單來說就是一個互斥體(或者說鎖),這樣的機制只允許一個線程來控制Python解釋器。這就意味著在任何一個時間點只有一個線程處于執(zhí)行狀態(tài)。

      所以在python中多線程是假的,因為在執(zhí)行過程中CPU中只有一個線程在執(zhí)行。

      當你使用多進程時,你的效率是高于多線程的。

      Python GIL經(jīng)常被認為是一個神秘而困難的話題,但是請記住作為一名Python支持者,只有當您正在編寫C擴展或者您的程序中有計算密集型的多線程任務時才會被GIL影響。

      Python 任務調(diào)度 多線程

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:AssetBundle分組策略
      下一篇:前端性能優(yōu)化之防抖與節(jié)流,大幅度降低你的事件處理性能
      相關(guān)文章
      亚洲AV人无码综合在线观看 | 亚洲成人福利网站| 国产亚洲精品美女久久久 | 亚洲AV日韩AV永久无码久久| 亚洲欧洲日产国码无码久久99| 亚洲人成影院在线无码观看| 亚洲片一区二区三区| 亚洲性日韩精品国产一区二区| 亚洲无码精品浪潮| 区久久AAA片69亚洲| 青青草原亚洲视频| 亚洲成AV人片在线观看无码| 亚洲va无码va在线va天堂| 久久久亚洲精品无码| 亚洲色av性色在线观无码| 亚洲最大在线视频| 亚洲一区二区三区国产精品无码| 久久久久亚洲精品日久生情 | 国产亚洲精品91| vvvv99日韩精品亚洲| 亚洲人午夜射精精品日韩| 亚洲色爱图小说专区| 国产精品亚洲片在线| 亚洲av无码成人黄网站在线观看 | 精品亚洲成A人在线观看青青| jizzjizz亚洲日本少妇| 亚洲精品网站在线观看不卡无广告 | 亚洲AV无码国产一区二区三区| 亚洲6080yy久久无码产自国产| | 国产亚洲精品欧洲在线观看| 亚洲A∨午夜成人片精品网站| 亚洲毛片av日韩av无码| 亚洲中文字幕无码不卡电影| 久久亚洲综合色一区二区三区| 亚洲伊人tv综合网色| 亚洲一区在线视频| 亚洲精品乱码久久久久久蜜桃图片 | 亚洲国产成人精品91久久久| 中文亚洲AV片不卡在线观看| 亚洲精品天天影视综合网|