Python 之 线程
進(jìn)程
之前我們已經(jīng)了解了操作系統(tǒng)中進(jìn)程的概念,程序并不能單獨運行,只有將程序裝載到內(nèi)存中,系統(tǒng)為它分配資源才能運行,而這種執(zhí)行的程序就稱之為進(jìn)程。程序和進(jìn)程的區(qū)別就在于:程序是指令的集合,它是進(jìn)程運行的靜態(tài)描述文本;進(jìn)程是程序的一次執(zhí)行活動,屬于動態(tài)概念。在多道編程中,我們允許多個程序同時加載到內(nèi)存中,在操作系統(tǒng)的調(diào)度下,可以實現(xiàn)并發(fā)地執(zhí)行。這樣的設(shè)計,大大提高了CPU的利用率。進(jìn)程的出現(xiàn)讓每個用戶感覺到自己獨享CPU,因此,進(jìn)程就是為了在CPU上實現(xiàn)多道編程而提出的。
有了進(jìn)程為什么要有線程
進(jìn)程有很多優(yōu)點,它提供了多道編程,讓我們感覺我們每個人都擁有自己的CPU和其他資源,可以提高計算機(jī)的利用率。很多人就不理解了,既然進(jìn)程這么優(yōu)秀,為什么還要線程呢?其實,仔細(xì)觀察就會發(fā)現(xiàn)進(jìn)程還是有很多缺陷的,主要體現(xiàn)在兩點上:
- 進(jìn)程只能在一個時間干一件事,如果想同時干兩件事或多件事,進(jìn)程就無能為力了。
- 進(jìn)程在執(zhí)行的過程中如果阻塞,例如等待輸入,整個進(jìn)程就會掛起,即使進(jìn)程中有些工作不依賴于輸入的數(shù)據(jù),也將無法執(zhí)行。
如果這兩個缺點理解比較困難的話,舉個現(xiàn)實的例子也許你就清楚了:如果把我們上課的過程看成一個進(jìn)程的話,那么我們要做的是耳朵聽老師講課,手上還要記筆記,腦子還要思考問題,這樣才能高效的完成聽課的任務(wù)。而如果只提供進(jìn)程這個機(jī)制的話,上面這三件事將不能同時執(zhí)行,同一時間只能做一件事,聽的時候就不能記筆記,也不能用腦子思考,這是其一;如果老師在黑板上寫演算過程,我們開始記筆記,而老師突然有一步推不下去了,阻塞住了,他在那邊思考著,而我們呢,也不能干其他事,即使你想趁此時思考一下剛才沒聽懂的一個問題都不行,這是其二。
現(xiàn)在你應(yīng)該明白了進(jìn)程的缺陷了,而解決的辦法很簡單,我們完全可以讓聽、寫、思三個獨立的過程,并行起來,這樣很明顯可以提高聽課的效率。而實際的操作系統(tǒng)中,也同樣引入了這種類似的機(jī)制——線程。
線程的出現(xiàn)
60年代,在OS中能擁有資源和獨立運行的基本單位是進(jìn)程,然而隨著計算機(jī)技術(shù)的發(fā)展,進(jìn)程出現(xiàn)了很多弊端,一是由于進(jìn)程是資源擁有者,創(chuàng)建、撤消與切換存在較大的時空開銷,因此需要引入輕型進(jìn)程;二是由于對稱多處理機(jī)(SMP)出現(xiàn),可以滿足多個運行單位,而多個進(jìn)程并行開銷過大。因此在80年代,出現(xiàn)了能獨立運行的基本單位——線程(Threads)。
注意:進(jìn)程是資源分配的最小單位,線程是CPU調(diào)度的最小單位.?每一個進(jìn)程中至少有一個線程。
進(jìn)程和線程的關(guān)系
線程與進(jìn)程的區(qū)別可以歸納為以下4點:
- 地址空間和其它資源(如打開文件):進(jìn)程間相互獨立,同一進(jìn)程的各線程間共享。某進(jìn)程內(nèi)的線程在其它進(jìn)程不可見。
- 通信:進(jìn)程間通信IPC,線程間可以直接讀寫進(jìn)程數(shù)據(jù)段(如全局變量)來進(jìn)行通信——需要進(jìn)程同步和互斥手段的輔助,以保證數(shù)據(jù)的一致性。
- 調(diào)度和切換:線程上下文切換比進(jìn)程上下文切換要快得多。
- 在多線程操作系統(tǒng)中,進(jìn)程不是一個可執(zhí)行的實體。
*通過漫畫了解線程進(jìn)程
線程的特點
在多線程的操作系統(tǒng)中,通常是在一個進(jìn)程中包括多個線程,每個線程都是作為利用CPU的基本單位,是花費最小開銷的實體。線程具有以下屬性。
1)輕型實體
線程中的實體基本上不擁有系統(tǒng)資源,只是有一點必不可少的、能保證獨立運行的資源。線程的實體包括程序、數(shù)據(jù)和TCB。線程是動態(tài)概念,它的動態(tài)特性由線程控制塊TCB(Thread Control Block)描述。
TCB包括以下信息:
(1)線程狀態(tài)。
(2)當(dāng)線程不運行時,被保存的現(xiàn)場資源。
(3)一組執(zhí)行堆棧。
(4)存放每個線程的局部變量主存區(qū)。
(5)訪問同一個進(jìn)程中的主存和其它資源。
用于指示被執(zhí)行指令序列的程序計數(shù)器、保留局部變量、少數(shù)狀態(tài)參數(shù)和返回地址等的一組寄存器和堆棧。
2)獨立調(diào)度和分派的基本單位。
在多線程OS中,線程是能獨立運行的基本單位,因而也是獨立調(diào)度和分派的基本單位。由于線程很“輕”,故線程的切換非常迅速且開銷小(在同一進(jìn)程中的)。
3)共享進(jìn)程資源。
線程在同一進(jìn)程中的各個線程,都可以共享該進(jìn)程所擁有的資源,這首先表現(xiàn)在:所有線程都具有相同的進(jìn)程id,這意味著,線程可以訪問該進(jìn)程的每一個內(nèi)存資源;此外,還可以訪問進(jìn)程所擁有的已打開文件、定時器、信號量機(jī)構(gòu)等。由于同一個進(jìn)程內(nèi)的線程共享內(nèi)存和文件,所以線程之間互相通信不必調(diào)用內(nèi)核。
4)可并發(fā)執(zhí)行。
在一個進(jìn)程中的多個線程之間,可以并發(fā)執(zhí)行,甚至允許在一個進(jìn)程中所有線程都能并發(fā)執(zhí)行;同樣,不同進(jìn)程中的線程也能并發(fā)執(zhí)行,充分利用和發(fā)揮了處理機(jī)與外圍設(shè)備并行工作的能力。
使用線程的實際場景
開啟一個字處理軟件進(jìn)程,該進(jìn)程肯定需要辦不止一件事情,比如監(jiān)聽鍵盤輸入,處理文字,定時自動將文字保存到硬盤,這三個任務(wù)操作的都是同一塊數(shù)據(jù),因而不能用多進(jìn)程。只能在一個進(jìn)程里并發(fā)地開啟三個線程,如果是單線程,那就只能是,鍵盤輸入時,不能處理文字和自動保存,自動保存時又不能輸入和處理文字。
內(nèi)存中的線程
多個線程共享同一個進(jìn)程的地址空間中的資源,是對一臺計算機(jī)上多個進(jìn)程的模擬,有時也稱線程為輕量級的進(jìn)程。而對一臺計算機(jī)上多個進(jìn)程,則共享物理內(nèi)存、磁盤、打印機(jī)等其他物理資源。多線程的運行也多進(jìn)程的運行類似,是cpu在多個線程之間的快速切換。
不同的進(jìn)程之間是充滿敵意的,彼此是搶占、競爭cpu的關(guān)系,如果迅雷會和QQ搶資源。而同一個進(jìn)程是由一個程序員的程序創(chuàng)建,所以同一進(jìn)程內(nèi)的線程是合作關(guān)系,一個線程可以訪問另外一個線程的內(nèi)存地址,大家都是共享的,一個線程干死了另外一個線程的內(nèi)存,那純屬程序員腦子有問題。
類似于進(jìn)程,每個線程也有自己的堆棧,不同于進(jìn)程,線程庫無法利用時鐘中斷強(qiáng)制線程讓出CPU,可以調(diào)用thread_yield運行線程自動放棄cpu,讓另外一個線程運行。
線程通常是有益的,但是帶來了不小程序設(shè)計難度,線程的問題是:
- 父進(jìn)程有多個線程,那么開啟的子線程是否需要同樣多的線程
- 在同一個進(jìn)程中,如果一個線程關(guān)閉了文件,而另外一個線程正準(zhǔn)備往該文件內(nèi)寫內(nèi)容呢?因此,在多線程的代碼中,需要更多的心思來設(shè)計程序的邏輯、保護(hù)程序的數(shù)據(jù)。
用戶級線程和內(nèi)核級線程(了解)
線程的實現(xiàn)可以分為兩類:用戶級線程(User-Level Thread)和內(nèi)核級線程(Kernel-Level Thread),后者又稱為內(nèi)核支持的線程或輕量級進(jìn)程。在多線程操作系統(tǒng)中,各個系統(tǒng)的實現(xiàn)方式并不相同,在有的系統(tǒng)中實現(xiàn)了用戶級線程,有的系統(tǒng)中實現(xiàn)了內(nèi)核級線程。
用戶級線程
內(nèi)核的切換由用戶態(tài)程序自己控制內(nèi)核切換,不需要內(nèi)核干涉,少了進(jìn)出內(nèi)核態(tài)的消耗,但不能很好的利用多核Cpu。
在用戶空間模擬操作系統(tǒng)對進(jìn)程的調(diào)度,來調(diào)用一個進(jìn)程中的線程,每個進(jìn)程中都會有一個運行時系統(tǒng),用來調(diào)度線程。此時當(dāng)該進(jìn)程獲取cpu時,進(jìn)程內(nèi)再調(diào)度出一個線程去執(zhí)行,同一時刻只有一個線程執(zhí)行。
內(nèi)核級線程
內(nèi)核級線程:切換由內(nèi)核控制,當(dāng)線程進(jìn)行切換的時候,由用戶態(tài)轉(zhuǎn)化為內(nèi)核態(tài)。切換完畢要從內(nèi)核態(tài)返回用戶態(tài);可以很好的利用smp,即利用多核cpu。windows線程就是這樣的。
用戶級與內(nèi)核級線程的對比
內(nèi)核進(jìn)程優(yōu)缺點
優(yōu)點:當(dāng)有多個處理機(jī)時,一個進(jìn)程的多個線程可以同時執(zhí)行。
缺點:由內(nèi)核進(jìn)行調(diào)度。
用戶進(jìn)程優(yōu)點:
- 線程的調(diào)度不需要內(nèi)核直接參與,控制簡單。
- 可以在不支持線程的操作系統(tǒng)中實現(xiàn)。
- 創(chuàng)建和銷毀線程、線程切換代價等線程管理的代價比內(nèi)核線程少得多。
- 允許每個進(jìn)程定制自己的調(diào)度算法,線程管理比較靈活。
- 線程能夠利用的表空間和堆棧空間比內(nèi)核級線程多。
- 同一進(jìn)程中只能同時有一個線程在運行,如果有一個線程使用了系統(tǒng)調(diào)用而阻塞,那么整個進(jìn)程都會被掛起。另外,頁面失效也會產(chǎn)生同樣的問題。
缺點:資源調(diào)度按照進(jìn)程進(jìn)行,多個處理機(jī)下,同一個進(jìn)程中的線程只能在同一個處理機(jī)下分時復(fù)用
混合實現(xiàn)
用戶級與內(nèi)核級的多路復(fù)用,內(nèi)核同一調(diào)度內(nèi)核線程,每個內(nèi)核線程對應(yīng)n個用戶線程
linux操作系統(tǒng)的 NPTL
歷史
在內(nèi)核2.6以前的調(diào)度實體都是進(jìn)程,內(nèi)核并沒有真正支持線程。它是能過一個系統(tǒng)調(diào)用clone()來實現(xiàn)的,這個調(diào)用創(chuàng)建了一份調(diào)用進(jìn)程的拷貝,跟fork()不同的是,這份進(jìn)程拷貝完全共享了調(diào)用進(jìn)程的地址空間。LinuxThread就是通過這個系統(tǒng)調(diào)用來提供線程在內(nèi)核級的支持的(許多以前的線程實現(xiàn)都完全是在用戶態(tài),內(nèi)核根本不知道線程的存在)。非常不幸的是,這種方法有相當(dāng)多的地方?jīng)]有遵循POSIX標(biāo)準(zhǔn),特別是在信號處理,調(diào)度,進(jìn)程間通信原語等方面。
很顯然,為了改進(jìn)LinuxThread必須得到內(nèi)核的支持,并且需要重寫線程庫。為了實現(xiàn)這個需求,開始有兩個相互競爭的項目:IBM啟動的NGTP(Next Generation POSIX Threads)項目,以及Redhat公司的NPTL。在2003年的年中,IBM放棄了NGTP,也就是大約那時,Redhat發(fā)布了最初的NPTL。
NPTL最開始在redhat linux 9里發(fā)布,現(xiàn)在從RHEL3起內(nèi)核2.6起都支持NPTL,并且完全成了GNU C庫的一部分。
NPTL使用了跟LinuxThread相同的辦法,在內(nèi)核里面線程仍然被當(dāng)作是一個進(jìn)程,并且仍然使用了clone()系統(tǒng)調(diào)用(在NPTL庫里調(diào)用)。但是,NPTL需要內(nèi)核級的特殊支持來實現(xiàn),比如需要掛起然后再喚醒線程的線程同步原語futex.
NPTL也是一個1*1的線程庫,就是說,當(dāng)你使用pthread_create()調(diào)用創(chuàng)建一個線程后,在內(nèi)核里就相應(yīng)創(chuàng)建了一個調(diào)度實體,在linux里就是一個新進(jìn)程,這個方法最大可能的簡化了線程的實現(xiàn)。
除NPTL的1*1模型外還有一個m*n模型,通常這種模型的用戶線程數(shù)會比內(nèi)核的調(diào)度實體多。在這種實現(xiàn)里,線程庫本身必須去處理可能存在的調(diào)度,這樣在線程庫內(nèi)部的上下文切換通常都會相當(dāng)?shù)目?#xff0c;因為它避免了系統(tǒng)調(diào)用轉(zhuǎn)到內(nèi)核態(tài)。然而這種模型增加了線程實現(xiàn)的復(fù)雜性,并可能出現(xiàn)諸如優(yōu)先級反轉(zhuǎn)的問題,此外,用戶態(tài)的調(diào)度如何跟內(nèi)核態(tài)的調(diào)度進(jìn)行協(xié)調(diào)也是很難讓人滿意。
線程和python
理論知識
全局解釋器鎖GIL
Python代碼的執(zhí)行由Python虛擬機(jī)(也叫解釋器主循環(huán))來控制。Python在設(shè)計之初就考慮到要在主循環(huán)中,同時只有一個線程在執(zhí)行。雖然 Python 解釋器中可以“運行”多個線程,但在任意時刻只有一個線程在解釋器中運行。
對Python虛擬機(jī)的訪問由全局解釋器鎖(GIL)來控制,正是這個鎖能保證同一時刻只有一個線程在運行。
在多線程環(huán)境中,Python 虛擬機(jī)按以下方式執(zhí)行:
在調(diào)用外部代碼(如 C/C++擴(kuò)展函數(shù))的時候,GIL將會被鎖定,直到這個函數(shù)結(jié)束為止(由于在這期間沒有Python的字節(jié)碼被運行,所以不會做線程切換)編寫擴(kuò)展的程序員可以主動解鎖GIL。
python的線程模塊
Python提供了幾個用于多線程編程的模塊,包括thread、threading和Queue等。thread和threading模塊允許程序員創(chuàng)建和管理線程。thread模塊提供了基本的線程和鎖的支持,threading提供了更高級別、功能更強(qiáng)的線程管理的功能。Queue模塊允許用戶創(chuàng)建一個可以用于多個線程之間共享數(shù)據(jù)的隊列數(shù)據(jù)結(jié)構(gòu)。
避免使用thread模塊,因為更高級別的threading模塊更為先進(jìn),對線程的支持更為完善,而且使用thread模塊里的屬性有可能會與threading出現(xiàn)沖突;其次低級別的thread模塊的同步原語很少(實際上只有一個),而threading模塊則有很多;再者,thread模塊中當(dāng)主線程結(jié)束時,所有的線程都會被強(qiáng)制結(jié)束掉,沒有警告也不會有正常的清除工作,至少threading模塊能確保重要的子線程退出后進(jìn)程才退出。
thread模塊不支持守護(hù)線程,當(dāng)主線程退出時,所有的子線程不論它們是否還在工作,都會被強(qiáng)行退出。而threading模塊支持守護(hù)線程,守護(hù)線程一般是一個等待客戶請求的服務(wù)器,如果沒有客戶提出請求它就在那等著,如果設(shè)定一個線程為守護(hù)線程,就表示這個線程是不重要的,在進(jìn)程退出的時候,不用等待這個線程退出。
threading模塊
multiprocess模塊完全模仿了threading模塊的接口,二者在使用層面,有很大的相似性,因而不再詳細(xì)介紹(官方鏈接)
線程的創(chuàng)建 Threading.Thread類
# 線程的創(chuàng)建 from threading import Thread import time def sayhi(name):time.sleep(2)print('%s say hello' %name)if __name__ == '__main__':t = Thread(target=sayhi,args=('egon',))t.start()print('主線程')# 通過繼承的方式創(chuàng)建 from threading import Thread import time class Sayhi(Thread):def __init__(self,name):super().__init__()self.name = namedef run(self):time.sleep(2)print('%s say hello' % self.name)if __name__ == '__main__':t = Sayhi('egon')t.start()print('主線程')多線程與多進(jìn)程
# pid對比 from threading import Thread from multiprocessing import Process import osdef work():print('hello',os.getpid())if __name__ == '__main__':# part1:在主進(jìn)程下開啟多個線程,每個線程都跟主進(jìn)程的pid一樣t1 = Thread(target=work)t2 = Thread(target=work)t1.start()t2.start()print('主線程/主進(jìn)程pid',os.getpid())# part2:開多個進(jìn)程,每個進(jìn)程都有不同的pidp1=Process(target=work)p2=Process(target=work)p1.start()p2.start()print('主線程/主進(jìn)程pid',os.getpid())# 效率對比 from threading import Thread from multiprocessing import Process import osdef work():print('hello')if __name__ == '__main__':# 在主進(jìn)程下開啟線程t = Thread(target=work)t.start()print('主線程/主進(jìn)程')'''打印結(jié)果:hello 主線程/主進(jìn)程'''# 在主進(jìn)程下開啟子進(jìn)程t = Process(target=work)t.start()print('主線程/主進(jìn)程')'''打印結(jié)果:主線程/主進(jìn)程hello'''# 內(nèi)存共享問題對比 from threading import Thread from multiprocessing import Process import os def work():global nn=0if __name__ == '__main__':# n=100# p = Process(target=work)# p.start()# p.join()# print('主',n) # 毫無疑問子進(jìn)程p已經(jīng)將自己的全局的n改成了0,但改的僅僅是它自己的,查看父進(jìn)程的n仍然為100n=1t = Thread(target=work)t.start()t.join()print('主',n) # 查看結(jié)果為0,因為同一進(jìn)程內(nèi)的線程之間共享進(jìn)程內(nèi)的數(shù)據(jù)練習(xí) :多線程實現(xiàn)socket
# server端 #_*_coding:utf-8_*_ #!/usr/bin/env python import multiprocessing import threadingimport socket s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind(('127.0.0.1',8080)) s.listen(5)def action(conn):while True:data=conn.recv(1024)print(data)conn.send(data.upper())if __name__ == '__main__':while True:conn,addr=s.accept()p=threading.Thread(target=action,args=(conn,))p.start()# client 端 #_*_coding:utf-8_*_ #!/usr/bin/env pythonimport sockets=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(('127.0.0.1',8080))while True:msg=input('>>: ').strip()if not msg:continues.send(msg.encode('utf-8'))data=s.recv(1024)print(data)Thread類的其他方法
Thread實例對象的方法
? # isAlive(): 返回線程是否活動的。
? # getName(): 返回線程名。
? # setName(): 設(shè)置線程名。
threading模塊提供的一些方法:
? # threading.currentThread(): 返回當(dāng)前的線程變量。
? # threading.enumerate(): 返回一個包含正在運行的線程的list。正在運行指線程啟動后、結(jié)束前,不包括啟動前和終止后的線程。
? # threading.activeCount(): 返回正在運行的線程數(shù)量,與len(threading.enumerate())有相同的結(jié)果。
守護(hù)線程
無論是進(jìn)程還是線程,都遵循:守護(hù)xx會等待主xx運行完畢后被銷毀。需要強(qiáng)調(diào)的是:運行完畢并非終止運行
- 對主進(jìn)程來說,運行完畢指的是主進(jìn)程代碼運行完畢;主進(jìn)程在其代碼結(jié)束后就已經(jīng)算運行完畢了(守護(hù)進(jìn)程在此時就被回收),然后主進(jìn)程會一直等非守護(hù)的子進(jìn)程都運行完畢后回收子進(jìn)程的資源(否則會產(chǎn)生僵尸進(jìn)程),才會結(jié)束,
- 對主線程來說,運行完畢指的是主線程所在的進(jìn)程內(nèi)所有非守護(hù)線程統(tǒng)統(tǒng)運行完畢;主線程在其他非守護(hù)線程運行完畢后才算運行完畢(守護(hù)線程在此時就被回收)。因為主線程的結(jié)束意味著進(jìn)程的結(jié)束,進(jìn)程整體的資源都將被回收,而進(jìn)程必須保證非守護(hù)線程都運行完畢后才能結(jié)束。
鎖
鎖與GIL
同步鎖
# 不加鎖:并發(fā)執(zhí)行,速度快,數(shù)據(jù)不安全 from threading import current_thread,Thread,Lock import os,time def task():global nprint('%s is running' %current_thread().getName())temp=ntime.sleep(0.5)n=temp-1if __name__ == '__main__':n=100lock=Lock()threads=[]start_time=time.time()for i in range(100):t=Thread(target=task)threads.append(t)t.start()for t in threads:t.join()stop_time=time.time()print('主:%s n:%s' %(stop_time-start_time,n))''' Thread-1 is running Thread-2 is running ...... Thread-100 is running 主:0.5216062068939209 n:99 '''# 不加鎖:未加鎖部分并發(fā)執(zhí)行,加鎖部分串行執(zhí)行,速度慢,數(shù)據(jù)安全 from threading import current_thread,Thread,Lock import os,time def task():# 未加鎖的代碼并發(fā)運行time.sleep(3)print('%s start to run' %current_thread().getName())global n# 加鎖的代碼串行運行l(wèi)ock.acquire()temp=ntime.sleep(0.5)n=temp-1lock.release()if __name__ == '__main__':n=100lock=Lock()threads=[]start_time=time.time()for i in range(100):t=Thread(target=task)threads.append(t)t.start()for t in threads:t.join()stop_time=time.time()print('主:%s n:%s' %(stop_time-start_time,n))''' Thread-1 is running Thread-2 is running ...... Thread-100 is running 主:53.294203758239746 n:0 '''# 既然加鎖會讓運行變成串行,那么在start之后立即使用join,就不用加鎖了啊,也是串行的效果啊 # 沒錯:在start之后立刻使用jion,肯定會將100個任務(wù)的執(zhí)行變成串行,毫無疑問,最終n的結(jié)果也肯定是0,是安全的,但是 # start后立即join:任務(wù)內(nèi)的所有代碼都是串行執(zhí)行的,而加鎖,只是加鎖的部分即修改共享數(shù)據(jù)的部分是串行的 # 單從保證數(shù)據(jù)安全方面,二者都可以實現(xiàn),但很明顯是加鎖的效率更高. from threading import current_thread,Thread,Lock import os,time def task():time.sleep(3)print('%s start to run' %current_thread().getName())global ntemp = ntime.sleep(0.5)n = temp-1if __name__ == '__main__':n=100lock=Lock()start_time=time.time()for i in range(100):t=Thread(target=task)t.start()t.join()stop_time=time.time()print('主:%s n:%s' %(stop_time-start_time,n))''' Thread-1 start to run Thread-2 start to run ...... Thread-100 start to run 主:350.6937336921692 n:0 # 耗時是多么的恐怖 '''死鎖與遞歸鎖
進(jìn)程也有死鎖與遞歸鎖,在進(jìn)程那里忘記說了,放到這里一切說了額
所謂死鎖: 是指兩個或兩個以上的進(jìn)程或線程在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程,如下就是死鎖
from threading import Lock as Lock import time mutexA = Lock() mutexA.acquire() mutexA.acquire() print(123) mutexA.release() mutexA.release()解決方法,遞歸鎖,在Python中為了支持在同一線程中多次請求同一資源,python提供了可重入鎖RLock。
這個RLock內(nèi)部維護(hù)著一個Lock和一個counter變量,counter記錄了acquire的次數(shù),從而使得資源可以被多次require。直到一個線程所有的acquire都被release,其他的線程才能獲得資源。上面的例子如果使用RLock代替Lock,則不會發(fā)生死鎖:
典型問題:科學(xué)家吃面
import time from threading import Thread,RLock fork_lock = noodle_lock = RLock() def eat1(name):noodle_lock.acquire()print('%s 搶到了面條'%name)fork_lock.acquire()print('%s 搶到了叉子'%name)print('%s 吃面'%name)fork_lock.release()noodle_lock.release()def eat2(name):fork_lock.acquire()print('%s 搶到了叉子' % name)time.sleep(1)noodle_lock.acquire()print('%s 搶到了面條' % name)print('%s 吃面' % name)noodle_lock.release()fork_lock.release()for name in ['哪吒','egon','yuan']:t1 = Thread(target=eat1,args=(name,))t2 = Thread(target=eat2,args=(name,))t1.start()t2.start()線程隊列
queue隊列 :使用import queue,用法與進(jìn)程Queue一樣
queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.
class?queue.Queue(maxsize=0) # 先進(jìn)先出
import queueq = queue.Queue() q.put('first') q.put('second') q.put('third')print(q.get()) print(q.get()) print(q.get()) ''' 結(jié)果(先進(jìn)先出): first second third '''class?queue.LifoQueue(maxsize=0) # last in fisrt out
import queueq = queue.LifoQueue() q.put('first') q.put('second') q.put('third')print(q.get()) print(q.get()) print(q.get()) ''' 結(jié)果(后進(jìn)先出): third second first '''class?queue.PriorityQueue(maxsize=0) # 存儲數(shù)據(jù)時可設(shè)置優(yōu)先級的隊列
import queueq = queue.PriorityQueue() # put進(jìn)入一個元組,元組的第一個元素是優(yōu)先級(通常是數(shù)字,也可以是非數(shù)字之間的比較),數(shù)字越小優(yōu)先級越高 q.put((20,'a')) q.put((10,'b')) q.put((30,'c'))print(q.get()) print(q.get()) print(q.get()) ''' 結(jié)果(數(shù)字越小優(yōu)先級越高,優(yōu)先級高的優(yōu)先出隊): (10, 'b') (20, 'a') (30, 'c') '''Python標(biāo)準(zhǔn)模塊--concurrent.futures
https://docs.python.org/dev/library/concurrent.futures.html
# 1 介紹 concurrent.futures模塊提供了高度封裝的異步調(diào)用接口 ThreadPoolExecutor:線程池,提供異步調(diào)用 ProcessPoolExecutor: 進(jìn)程池,提供異步調(diào)用 Both implement the same interface, which is defined by the abstract Executor class.# 2 基本方法 # submit(fn, *args, **kwargs) 異步提交任務(wù)# map(func, *iterables, timeout=None, chunksize=1) 取代for循環(huán)submit的操作# shutdown(wait=True) 相當(dāng)于進(jìn)程池的pool.close()+pool.join()操作 wait=True,等待池內(nèi)所有任務(wù)執(zhí)行完畢回收完資源后才繼續(xù) wait=False,立即返回,并不會等待池內(nèi)的任務(wù)執(zhí)行完畢 但不管wait參數(shù)為何值,整個程序都會等到所有任務(wù)執(zhí)行完畢 submit和map必須在shutdown之前# result(timeout=None) 取得結(jié)果# add_done_callback(fn) 回調(diào)函數(shù)# done() 判斷某一個線程是否完成# cancle() 取消某個任務(wù) # 用法 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutorimport os,time,random def task(n):print('%s is runing' %os.getpid())time.sleep(random.randint(1,3))return n**2if __name__ == '__main__':executor = ProcessPoolExecutor(max_workers=3)futures=[]for i in range(11):future = executor.submit(task,i)futures.append(future)executor.shutdown(True)print('+++>')for future in futures:print(future.result()) # join用法 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutorimport os,time,random def task(n):print('%s is runing' %os.getpid())time.sleep(random.randint(1,3))return n**2if __name__ == '__main__':executor=ThreadPoolExecutor(max_workers=3)# for i in range(11):# future=executor.submit(task,i)executor.map(task,range(1,12)) # map取代了for+submit # 回調(diào)函數(shù) from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor from multiprocessing import Pool import requests import json import osdef get_page(url):print('<進(jìn)程%s> get %s' %(os.getpid(),url))respone=requests.get(url)if respone.status_code == 200:return {'url':url,'text':respone.text}def parse_page(res):res=res.result()print('<進(jìn)程%s> parse %s' %(os.getpid(),res['url']))parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))with open('db.txt','a') as f:f.write(parse_res)if __name__ == '__main__':urls=['https://www.baidu.com','https://www.python.org','https://www.openstack.org','https://help.github.com/','http://www.sina.com.cn/']# p = Pool(3)# for url in urls:# p.apply_async(get_page,args=(url,),callback=pasrse_page)# p.close()# p.join()p=ProcessPoolExecutor(3)for url in urls:p.submit(get_page,url).add_done_callback(parse_page) # parse_page拿到的是一個future對象obj,需要用obj.result()拿到結(jié)果總結(jié)
以上是生活随笔為你收集整理的Python 之 线程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用适用于Windows的Ultimat
- 下一篇: python中分支结构包括哪些_pyth