摘要:某進(jìn)程內(nèi)的線程在其它進(jìn)程不可見。線程的實(shí)體包括程序數(shù)據(jù)和。包括以下信息線程狀態(tài)。當(dāng)線程不運(yùn)行時(shí),被保存的現(xiàn)場(chǎng)資源。用戶級(jí)線程執(zhí)行系統(tǒng)調(diào)用指令時(shí)將導(dǎo)致其所屬進(jìn)程被中斷,而內(nèi)核支持線程執(zhí)行系統(tǒng)調(diào)用指令時(shí),只導(dǎo)致該線程被中斷。線程能夠利用的表空
操作系統(tǒng)線程理論 線程概念的引入背景
進(jìn)程
之前我們已經(jīng)了解了操作系統(tǒng)中進(jìn)程的概念,程序并不能多帶帶運(yùn)行,只有將程序裝載到內(nèi)存中,系統(tǒng)為它分配資源才能運(yùn)行,而這種執(zhí)行的程序就稱之為進(jìn)程。程序和進(jìn)程的區(qū)別就在于:程序是指令的集合,它是進(jìn)程運(yùn)行的靜態(tài)描述文本;進(jìn)程是程序的一次執(zhí)行活動(dòng),屬于動(dòng)態(tài)概念。在多道編程中,我們?cè)试S多個(gè)程序同時(shí)加載到內(nèi)存中,在操作系統(tǒng)的調(diào)度下,可以實(shí)現(xiàn)并發(fā)地執(zhí)行。這是這樣的設(shè)計(jì),大大提高了CPU的利用率。進(jìn)程的出現(xiàn)讓每個(gè)用戶感覺到自己獨(dú)享CPU,因此,進(jìn)程就是為了在CPU上實(shí)現(xiàn)多道編程而提出的。
有了進(jìn)程為什么要有線程
進(jìn)程有很多優(yōu)點(diǎn),它提供了多道編程,讓我們感覺我們每個(gè)人都擁有自己的CPU和其他資源,可以提高計(jì)算機(jī)的利用率。很多人就不理解了,既然進(jìn)程這么優(yōu)秀,為什么還要線程呢?其實(shí),仔細(xì)觀察就會(huì)發(fā)現(xiàn)進(jìn)程還是有很多缺陷的,主要體現(xiàn)在兩點(diǎn)上:
進(jìn)程只能在一個(gè)時(shí)間干一件事,如果想同時(shí)干兩件事或多件事,進(jìn)程就無(wú)能為力了。
進(jìn)程在執(zhí)行的過(guò)程中如果阻塞,例如等待輸入,整個(gè)進(jìn)程就會(huì)掛起,即使進(jìn)程中有些工作不依賴于輸入的數(shù)據(jù),也將無(wú)法執(zhí)行。
如果這兩個(gè)缺點(diǎn)理解比較困難的話,舉個(gè)現(xiàn)實(shí)的例子也許你就清楚了:如果把我們上課的過(guò)程看成一個(gè)進(jìn)程的話,那么我們要做的是耳朵聽老師講課,手上還要記筆記,腦子還要思考問(wèn)題,這樣才能高效的完成聽課的任務(wù)。而如果只提供進(jìn)程這個(gè)機(jī)制的話,上面這三件事將不能同時(shí)執(zhí)行,同一時(shí)間只能做一件事,聽的時(shí)候就不能記筆記,也不能用腦子思考,這是其一;如果老師在黑板上寫演算過(guò)程,我們開始記筆記,而老師突然有一步推不下去了,阻塞住了,他在那邊思考著,而我們呢,也不能干其他事,即使你想趁此時(shí)思考一下剛才沒(méi)聽懂的一個(gè)問(wèn)題都不行,這是其二。
現(xiàn)在你應(yīng)該明白了進(jìn)程的缺陷了,而解決的辦法很簡(jiǎn)單,我們完全可以讓聽、寫、思三個(gè)獨(dú)立的過(guò)程,并行起來(lái),這樣很明顯可以提高聽課的效率。而實(shí)際的操作系統(tǒng)中,也同樣引入了這種類似的機(jī)制——線程。
線程的出現(xiàn)
60年代,在OS中能擁有資源和獨(dú)立運(yùn)行的基本單位是進(jìn)程,然而隨著計(jì)算機(jī)技術(shù)的發(fā)展,進(jìn)程出現(xiàn)了很多弊端,一是由于進(jìn)程是資源擁有者,創(chuàng)建、撤消與切換存在較大的時(shí)空開銷,因此需要引入輕型進(jìn)程;二是由于對(duì)稱多處理機(jī)(SMP)出現(xiàn),可以滿足多個(gè)運(yùn)行單位,而多個(gè)進(jìn)程并行開銷過(guò)大。
因此在80年代,出現(xiàn)了能獨(dú)立運(yùn)行的基本單位——線程(Threads)。
注意:進(jìn)程是資源分配的最小單位,線程是CPU調(diào)度的最小單位.
每一個(gè)進(jìn)程中至少有一個(gè)線程?!?/p>
進(jìn)程和線程的關(guān)系
線程與進(jìn)程的區(qū)別可以歸納為以下4點(diǎn):
1)地址空間和其它資源(如打開文件):進(jìn)程間相互獨(dú)立,同一進(jìn)程的各線程間共享。某進(jìn)程內(nèi)的線程在其它進(jìn)程不可見。
2)通信:進(jìn)程間通信IPC,線程間可以直接讀寫進(jìn)程數(shù)據(jù)段(如全局變量)來(lái)進(jìn)行通信——需要進(jìn)程同步和互斥手段的輔助,以保證數(shù)據(jù)的一致性。
3)調(diào)度和切換:線程上下文切換比進(jìn)程上下文切換要快得多。
4)在多線程操作系統(tǒng)中,進(jìn)程不是一個(gè)可執(zhí)行的實(shí)體。
*通過(guò)漫畫了解線程進(jìn)城
1)輕型實(shí)體
線程中的實(shí)體基本上不擁有系統(tǒng)資源,只是有一點(diǎn)必不可少的、能保證獨(dú)立運(yùn)行的資源。
線程的實(shí)體包括程序、數(shù)據(jù)和TCB。線程是動(dòng)態(tài)概念,它的動(dòng)態(tài)特性由線程控制塊TCB(Thread Control Block)描述。
TCB包括以下信息:
(1)線程狀態(tài)。
(2)當(dāng)線程不運(yùn)行時(shí),被保存的現(xiàn)場(chǎng)資源。
(3)一組執(zhí)行堆棧。
(4)存放每個(gè)線程的局部變量主存區(qū)。
(5)訪問(wèn)同一個(gè)進(jìn)程中的主存和其它資源。
用于指示被執(zhí)行指令序列的程序計(jì)數(shù)器、保留局部變量、少數(shù)狀態(tài)參數(shù)和返回地址等的一組寄存器和堆棧。
復(fù)制代碼
2)獨(dú)立調(diào)度和分派的基本單位。
在多線程OS中,線程是能獨(dú)立運(yùn)行的基本單位,因而也是獨(dú)立調(diào)度和分派的基本單位。由于線程很“輕”,故線程的切換非常迅速且開銷小(在同一進(jìn)程中的)。
3)共享進(jìn)程資源。
線程在同一進(jìn)程中的各個(gè)線程,都可以共享該進(jìn)程所擁有的資源,這首先表現(xiàn)在:所有線程都具有相同的進(jìn)程id,這意味著,線程可以訪問(wèn)該進(jìn)程的每一個(gè)內(nèi)存資源;此外,還可以訪問(wèn)進(jìn)程所擁有的已打開文件、定時(shí)器、信號(hào)量機(jī)構(gòu)等。由于同一個(gè)進(jìn)程內(nèi)的線程共享內(nèi)存和文件,所以線程之間互相通信不必調(diào)用內(nèi)核。
4)可并發(fā)執(zhí)行。
在一個(gè)進(jìn)程中的多個(gè)線程之間,可以并發(fā)執(zhí)行,甚至允許在一個(gè)進(jìn)程中所有線程都能并發(fā)執(zhí)行;同樣,不同進(jìn)程中的線程也能并發(fā)執(zhí)行,充分利用和發(fā)揮了處理機(jī)與外圍設(shè)備并行工作的能力。
開啟一個(gè)字處理軟件進(jìn)程,該進(jìn)程肯定需要辦不止一件事情,比如監(jiān)聽鍵盤輸入,處理文字,定時(shí)自動(dòng)將文字保存到硬盤,這三個(gè)任務(wù)操作的都是同一塊數(shù)據(jù),因而不能用多進(jìn)程。只能在一個(gè)進(jìn)程里并發(fā)地開啟三個(gè)線程,如果是單線程,那就只能是,鍵盤輸入時(shí),不能處理文字和自動(dòng)保存,自動(dòng)保存時(shí)又不能輸入和處理文字。
內(nèi)存中的線程多個(gè)線程共享同一個(gè)進(jìn)程的地址空間中的資源,是對(duì)一臺(tái)計(jì)算機(jī)上多個(gè)進(jìn)程的模擬,有時(shí)也稱線程為輕量級(jí)的進(jìn)程。
而對(duì)一臺(tái)計(jì)算機(jī)上多個(gè)進(jìn)程,則共享物理內(nèi)存、磁盤、打印機(jī)等其他物理資源。多線程的運(yùn)行也多進(jìn)程的運(yùn)行類似,是cpu在多個(gè)線程之間的快速切換。
不同的進(jìn)程之間是充滿敵意的,彼此是搶占、競(jìng)爭(zhēng)cpu的關(guān)系,如果迅雷會(huì)和QQ搶資源。而同一個(gè)進(jìn)程是由一個(gè)程序員的程序創(chuàng)建,所以同一進(jìn)程內(nèi)的線程是合作關(guān)系,一個(gè)線程可以訪問(wèn)另外一個(gè)線程的內(nèi)存地址,大家都是共享的,一個(gè)線程干死了另外一個(gè)線程的內(nèi)存,那純屬程序員腦子有問(wèn)題。
類似于進(jìn)程,每個(gè)線程也有自己的堆棧,不同于進(jìn)程,線程庫(kù)無(wú)法利用時(shí)鐘中斷強(qiáng)制線程讓出CPU,可以調(diào)用thread_yield運(yùn)行線程自動(dòng)放棄cpu,讓另外一個(gè)線程運(yùn)行。
線程通常是有益的,但是帶來(lái)了不小程序設(shè)計(jì)難度,線程的問(wèn)題是:
1. 父進(jìn)程有多個(gè)線程,那么開啟的子線程是否需要同樣多的線程
2. 在同一個(gè)進(jìn)程中,如果一個(gè)線程關(guān)閉了文件,而另外一個(gè)線程正準(zhǔn)備往該文件內(nèi)寫內(nèi)容呢?
因此,在多線程的代碼中,需要更多的心思來(lái)設(shè)計(jì)程序的邏輯、保護(hù)程序的數(shù)據(jù)。
線程的實(shí)現(xiàn)可以分為兩類:用戶級(jí)線程(User-Level Thread)和內(nèi)核線線程(Kernel-Level Thread),后者又稱為內(nèi)核支持的線程或輕量級(jí)進(jìn)程。在多線程操作系統(tǒng)中,各個(gè)系統(tǒng)的實(shí)現(xiàn)方式并不相同,在有的系統(tǒng)中實(shí)現(xiàn)了用戶級(jí)線程,有的系統(tǒng)中實(shí)現(xiàn)了內(nèi)核級(jí)線程。
用戶級(jí)線程
內(nèi)核的切換由用戶態(tài)程序自己控制內(nèi)核切換,不需要內(nèi)核干涉,少了進(jìn)出內(nèi)核態(tài)的消耗,但不能很好的利用多核Cpu。
內(nèi)核級(jí)線程
內(nèi)核級(jí)線程:切換由內(nèi)核控制,當(dāng)線程進(jìn)行切換的時(shí)候,由用戶態(tài)轉(zhuǎn)化為內(nèi)核態(tài)。切換完畢要從內(nèi)核態(tài)返回用戶態(tài);可以很好的利用smp,即利用多核cpu。windows線程就是這樣的。
用戶級(jí)與內(nèi)核級(jí)線程的對(duì)比
1.用戶級(jí)線程和內(nèi)核級(jí)線程的區(qū)別
1 內(nèi)核支持線程是OS內(nèi)核可感知的,而用戶級(jí)線程是OS內(nèi)核不可感知的。 2 用戶級(jí)線程的創(chuàng)建、撤消和調(diào)度不需要OS內(nèi)核的支持,是在語(yǔ)言(如Java)這一級(jí)處理的;而內(nèi)核支持線程的創(chuàng)建、撤消和調(diào)度都需OS內(nèi)核提供支持,而且與進(jìn)程的創(chuàng)建、撤消和調(diào)度大體是相同的。 3 用戶級(jí)線程執(zhí)行系統(tǒng)調(diào)用指令時(shí)將導(dǎo)致其所屬進(jìn)程被中斷,而內(nèi)核支持線程執(zhí)行系統(tǒng)調(diào)用指令時(shí),只導(dǎo)致該線程被中斷。 4 在只有用戶級(jí)線程的系統(tǒng)內(nèi),CPU調(diào)度還是以進(jìn)程為單位,處于運(yùn)行狀態(tài)的進(jìn)程中的多個(gè)線程,由用戶程序控制線程的輪換運(yùn)行;在有內(nèi)核支持線程的系統(tǒng)內(nèi),CPU調(diào)度則以線程為單位,由OS的線程調(diào)度程序負(fù)責(zé)線程的調(diào)度。 5 用戶級(jí)線程的程序?qū)嶓w是運(yùn)行在用戶態(tài)下的程序,而內(nèi)核支持線程的程序?qū)嶓w則是可以運(yùn)行在任何狀態(tài)下的程序。
2.內(nèi)核線程的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):當(dāng)有多個(gè)處理機(jī)時(shí),一個(gè)進(jìn)程的多個(gè)線程可以同時(shí)執(zhí)行。 缺點(diǎn):由內(nèi)核進(jìn)行調(diào)度。
3.用戶級(jí)線程的優(yōu)缺點(diǎn)
優(yōu)點(diǎn): 線程的調(diào)度不需要內(nèi)核直接參與,控制簡(jiǎn)單。 可以在不支持線程的操作系統(tǒng)中實(shí)現(xiàn)。 創(chuàng)建和銷毀線程、線程切換代價(jià)等線程管理的代價(jià)比內(nèi)核線程少得多。 允許每個(gè)進(jìn)程定制自己的調(diào)度算法,線程管理比較靈活。 線程能夠利用的表空間和堆??臻g比內(nèi)核級(jí)線程多。 同一進(jìn)程中只能同時(shí)有一個(gè)線程在運(yùn)行,如果有一個(gè)線程使用了系統(tǒng)調(diào)用而阻塞,那么整個(gè)進(jìn)程都會(huì)被掛起。另外,頁(yè)面失效也會(huì)產(chǎn)生同樣的問(wèn)題。 缺點(diǎn): 資源調(diào)度按照進(jìn)程進(jìn)行,多個(gè)處理機(jī)下,同一個(gè)進(jìn)程中的線程只能在同一個(gè)處理機(jī)下分時(shí)復(fù)用
混合實(shí)現(xiàn)
用戶級(jí)與內(nèi)核級(jí)的多路復(fù)用,內(nèi)核同一調(diào)度內(nèi)核線程,每個(gè)內(nèi)核線程對(duì)應(yīng)n個(gè)用戶線程
全局解釋器鎖GIL
Python代碼的執(zhí)行由Python虛擬機(jī)(也叫解釋器主循環(huán))來(lái)控制。Python在設(shè)計(jì)之初就考慮到要在主循環(huán)中,同時(shí)只有一個(gè)線程在執(zhí)行。雖然 Python 解釋器中可以“運(yùn)行”多個(gè)線程,但在任意時(shí)刻只有一個(gè)線程在解釋器中運(yùn)行。
對(duì)Python虛擬機(jī)的訪問(wèn)由全局解釋器鎖(GIL)來(lái)控制,正是這個(gè)鎖能保證同一時(shí)刻只有一個(gè)線程在運(yùn)行。
在多線程環(huán)境中,Python 虛擬機(jī)按以下方式執(zhí)行:
a、設(shè)置 GIL;
b、切換到一個(gè)線程去運(yùn)行;
c、運(yùn)行指定數(shù)量的字節(jié)碼指令或者線程主動(dòng)讓出控制(可以調(diào)用 time.sleep(0));
d、把線程設(shè)置為睡眠狀態(tài);
e、解鎖 GIL;
d、再次重復(fù)以上所有步驟。
在調(diào)用外部代碼(如 C/C++擴(kuò)展函數(shù))的時(shí)候,GIL將會(huì)被鎖定,直到這個(gè)函數(shù)結(jié)束為止(由于在這期間沒(méi)有Python的字節(jié)碼被運(yùn)行,所以不會(huì)做線程切換)編寫擴(kuò)展的程序員可以主動(dòng)解鎖GIL。
python線程模塊的選擇
Python提供了幾個(gè)用于多線程編程的模塊,包括thread、threading和Queue等。thread和threading模塊允許程序員創(chuàng)建和管理線程。thread模塊提供了基本的線程和鎖的支持,threading提供了更高級(jí)別、功能更強(qiáng)的線程管理的功能。Queue模塊允許用戶創(chuàng)建一個(gè)可以用于多個(gè)線程之間共享數(shù)據(jù)的隊(duì)列數(shù)據(jù)結(jié)構(gòu)。
避免使用thread模塊,因?yàn)楦呒?jí)別的threading模塊更為先進(jìn),對(duì)線程的支持更為完善,而且使用thread模塊里的屬性有可能會(huì)與threading出現(xiàn)沖突;其次低級(jí)別的thread模塊的同步原語(yǔ)很少(實(shí)際上只有一個(gè)),而threading模塊則有很多;再者,thread模塊中當(dāng)主線程結(jié)束時(shí),所有的線程都會(huì)被強(qiáng)制結(jié)束掉,沒(méi)有警告也不會(huì)有正常的清除工作,至少threading模塊能確保重要的子線程退出后進(jìn)程才退出。
thread模塊不支持守護(hù)線程,當(dāng)主線程退出時(shí),所有的子線程不論它們是否還在工作,都會(huì)被強(qiáng)行退出。而threading模塊支持守護(hù)線程,守護(hù)線程一般是一個(gè)等待客戶請(qǐng)求的服務(wù)器,如果沒(méi)有客戶提出請(qǐng)求它就在那等著,如果設(shè)定一個(gè)線程為守護(hù)線程,就表示這個(gè)線程是不重要的,在進(jìn)程退出的時(shí)候,不用等待這個(gè)線程退出。
multiprocess模塊的完全模仿了threading模塊的接口,二者在使用層面,有很大的相似性,見官網(wǎng)鏈接:
線程的創(chuàng)建Threading.Thread類
1.線程的創(chuàng)建
創(chuàng)建線程的方式1:
#!/usr/bin/env python # -*- coding:utf-8 -*- 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)建線程的方式2:
from threading import Thread import time class Sayhi(Thread): def __init__(self,name): super().__init__() self.name=name def run(self): time.sleep(2) print("%s say hello" % self.name) if __name__ == "__main__": t = Sayhi("egon") t.start() print("主線程")
2.多線程與多進(jìn)程
pid的比較
from threading import Thread from multiprocessing import Process import os def work(): print("hello",os.getpid()) if __name__ == "__main__": #part1:在主進(jìn)程下開啟多個(gè)線程,每個(gè)線程都跟主進(jìn)程的pid一樣 t1=Thread(target=work) t2=Thread(target=work) t1.start() t2.start() print("主線程/主進(jìn)程pid",os.getpid()) #part2:開多個(gè)進(jìn)程,每個(gè)進(jìn)程都有不同的pid p1=Process(target=work) p2=Process(target=work) p1.start() p2.start() print("主線程/主進(jìn)程pid",os.getpid())
開啟效率的較量
import time from multiprocessing import Process from threading import Thread n = 10 def func(i): global n n -= 1 if __name__ == "__main__": start = time.time() t_lst = [] for i in range(100): t = Thread(target=func,args=(i,)) t.start() t_lst.append(t) for t in t_lst:t.join() print("線程 ",time.time() - start) start = time.time() p_lst = [] for i in range(100): p = Process(target=func,args=(i,)) p.start() p_lst.append(p) for p in p_lst: p.join() print("進(jìn)程 :",time.time() - start)
內(nèi)存數(shù)據(jù)的共享問(wèn)題
from threading import Thread from multiprocessing import Process def work(): global n n=0 print("子線程/子進(jìn)程",n) if __name__ == "__main__": n=100 p=Process(target=work) p.start() p.join() print("主",n) #毫無(wú)疑問(wèn)子進(jìn)程p已經(jīng)將自己的全局的n改成了0,但改的僅僅是它自己的,查看父進(jìn)程的n仍然為100 n=1 t=Thread(target=work) t.start() t.join() print("主",n) #查看結(jié)果為0,因?yàn)橥贿M(jìn)程內(nèi)的線程之間共享進(jìn)程內(nèi)的數(shù)據(jù)
多線程實(shí)現(xiàn)socket
server端:
from threading import Thread import socket s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.bind(("127.0.0.1",8080)) s.listen(5) def action(conn,addr): while True: data=conn.recv(1024) print("來(lái)自客戶端:{addr}消息為:{data}".format(addr=addr,data=data.decode("utf-8"))) conn.send(data) if __name__ == "__main__": while True: conn,addr=s.accept() p=Thread(target=action,args=(conn,addr)) p.start()
client端:
import socket ip_port = ("127.0.0.1",8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(ip_port) while True: msg=input(">>: ").strip() if not msg:continue s.send(msg.encode("utf-8")) data=s.recv(1024).decode("utf-8") print("來(lái)自服務(wù)端:{ip},消息為:{data}".format(ip=ip_port,data=data))
Thread類的其他方法
Thread實(shí)例對(duì)象的方法 # isAlive(): 返回線程是否活動(dòng)的。 # getName(): 返回線程名。 # setName(): 設(shè)置線程名。 threading模塊提供的一些方法: # threading.currentThread(): 返回當(dāng)前的線程變量。 # threading.enumerate(): 返回一個(gè)包含正在運(yùn)行的線程的list。正在運(yùn)行指線程啟動(dòng)后、結(jié)束前,不包括啟動(dòng)前和終止后的線程。 # threading.activeCount(): 返回正在運(yùn)行的線程數(shù)量,與len(threading.enumerate())有相同的結(jié)果。
守護(hù)線程
無(wú)論是進(jìn)程還是線程,都遵循:守護(hù)xx會(huì)等待主xx運(yùn)行完畢后被銷毀。需要強(qiáng)調(diào)的是:運(yùn)行完畢并非終止運(yùn)行
1 主進(jìn)程在其代碼結(jié)束后就已經(jīng)算運(yùn)行完畢了(守護(hù)進(jìn)程在此時(shí)就被回收),然后主進(jìn)程會(huì)一直等非守護(hù)的子進(jìn)程都運(yùn)行完畢后回收子進(jìn)程的資源(否則會(huì)產(chǎn)生僵尸進(jìn)程),才會(huì)結(jié)束,
2 主線程在其他非守護(hù)線程運(yùn)行完畢后才算運(yùn)行完畢(守護(hù)線程在此時(shí)就被回收)。因?yàn)橹骶€程的結(jié)束意味著進(jìn)程的結(jié)束,進(jìn)程整體的資源都將被回收,而進(jìn)程必須保證非守護(hù)線程都運(yùn)行完畢后才能結(jié)束。
1.守護(hù)線程例1
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.setDaemon(True) #必須在t.start()之前設(shè)置 t.start() print("主線程") print(t.is_alive()) """ 主線程 True """
2.守護(hù)線程例2
from threading import Thread import time def foo(): print(123) time.sleep(1) print("end123") def bar(): print(456) time.sleep(3) print("end456") t1=Thread(target=foo) t2=Thread(target=bar) t1.daemon=True t1.start() t2.start() print("main-------")鎖
同步鎖
1.多個(gè)線程搶占資源的情況
from threading import Thread import os,time def work(): global n temp=n time.sleep(0.1) n=temp-1 if __name__ == "__main__": n=100 l=[] for i in range(100): p=Thread(target=work) l.append(p) p.start() for p in l: p.join() print(n) #結(jié)果可能為99
2.解決方法:
import threading R=threading.Lock() R.acquire() """ 對(duì)公共數(shù)據(jù)的操作 """ R.release()
3.同步鎖的引用
from threading import Thread,Lock import os,time def work(): global n lock.acquire() temp=n print("子進(jìn)程temp", temp) time.sleep(0.1) n=temp-1 print("子進(jìn)程n", n) lock.release() if __name__ == "__main__": lock=Lock() n=5 l=[] for i in range(5): p=Thread(target=work) l.append(p) p.start() for p in l: p.join() print(n)
3.互斥鎖與join的區(qū)別
#不加鎖:并發(fā)執(zhí)行,速度快,數(shù)據(jù)不安全 from threading import current_thread,Thread,Lock import os,time def task(): global n print("%s is running" %current_thread().getName()) temp=n time.sleep(0.5) n=temp-1 if __name__ == "__main__": n=100 lock=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ā)運(yùn)行 time.sleep(3) print("%s start to run" %current_thread().getName()) global n #加鎖的代碼串行運(yùn)行 lock.acquire() temp=n time.sleep(0.5) n=temp-1 lock.release() if __name__ == "__main__": n=100 lock=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 """ #有的同學(xué)可能有疑問(wèn):既然加鎖會(huì)讓運(yùn)行變成串行,那么我在start之后立即使用join,就不用加鎖了啊,也是串行的效果啊 #沒(méi)錯(cuò):在start之后立刻使用jion,肯定會(huì)將100個(gè)任務(wù)的執(zhí)行變成串行,毫無(wú)疑問(wèn),最終n的結(jié)果也肯定是0,是安全的,但問(wèn)題是 #start后立即join:任務(wù)內(nèi)的所有代碼都是串行執(zhí)行的,而加鎖,只是加鎖的部分即修改共享數(shù)據(jù)的部分是串行的 #單從保證數(shù)據(jù)安全方面,二者都可以實(shí)現(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 n temp=n time.sleep(0.5) n=temp-1 if __name__ == "__main__": n=100 lock=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 #耗時(shí)是多么的恐怖
4.死鎖與遞歸鎖
進(jìn)程也有死鎖與遞歸鎖
所謂死鎖: 是指兩個(gè)或兩個(gè)以上的進(jìn)程或線程在執(zhí)行過(guò)程中,因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象,若無(wú)外力作用,它們都將無(wú)法推進(jìn)下去。此時(shí)稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程,如下就是死鎖
import time from threading import Lock,Thread noodle = 100 fork = 100 noodle_lock = Lock() fork_lock = Lock() def eat1(name): global noodle,fork noodle_lock.acquire() print("%s拿到面了" % name) fork_lock.acquire() print("%s拿到叉子了" % name) noodle -= 1 print("%s吃面"%name) time.sleep(0.1) fork_lock.release() print("%s放下叉子了" % name) noodle_lock.release() print("%s放下面" % name) def eat2(name): global noodle,fork fork_lock.acquire() print("%s拿到叉子了"%name) noodle_lock.acquire() print("%s拿到面了"%name) noodle -= 1 print("%s吃面"%name) time.sleep(0.1) noodle_lock.release() print("%s放下面"%name) fork_lock.release() print("%s放下叉子了"%name) for i in ["alex","wusir","egon","快老師"]: Thread(target=eat1,args=(i,)).start() Thread(target=eat2,args=(i+"2",)).start()
解決方法,遞歸鎖,在Python中為了支持在同一線程中多次請(qǐng)求同一資源,python提供了可重入鎖RLock。
這個(gè)RLock內(nèi)部維護(hù)著一個(gè)Lock和一個(gè)counter變量,counter記錄了acquire的次數(shù),從而使得資源可以被多次require。直到一個(gè)線程所有的acquire都被release,其他的線程才能獲得資源。上面的例子如果使用RLock代替Lock,則不會(huì)發(fā)生死鎖:
import time from threading import Thread,RLock noodle = 100 fork = 100 noodle_lock = fork_lock = RLock() def eat1(name): global noodle,fork noodle_lock.acquire() print("%s拿到面了" % name) fork_lock.acquire() print("%s拿到叉子了" % name) noodle -= 1 print("%s吃面"%name) time.sleep(0.1) fork_lock.release() print("%s放下叉子了" % name) noodle_lock.release() print("%s放下面" % name) def eat2(name): global noodle,fork fork_lock.acquire() print("%s拿到叉子了"%name) noodle_lock.acquire() print("%s拿到面了"%name) noodle -= 1 print("%s吃面"%name) time.sleep(0.1) noodle_lock.release() print("%s放下面"%name) fork_lock.release() print("%s放下叉子了"%name) for i in ["alex","wusir","egon","快老師"]: Thread(target=eat1,args=(i,)).start() Thread(target=eat2,args=(i+"2",)).start()
使用互斥鎖解決死鎖問(wèn)題:
import time from threading import Thread,Lock noodle = 100 fork = 100 noodle_fork_lock = Lock() def eat1(name): global noodle,fork noodle_fork_lock.acquire() print("%s拿到面了" % name) print("%s拿到叉子了" % name) noodle -= 1 print("%s吃面"%name) time.sleep(0.1) print("%s放下叉子了" % name) noodle_fork_lock.release() print("%s放下面" % name) def eat2(name): global noodle,fork noodle_fork_lock.acquire() print("%s拿到叉子了"%name) print("%s拿到面了"%name) noodle -= 1 print("%s吃面"%name) time.sleep(0.1) print("%s放下面"%name) noodle_fork_lock.release() print("%s放下叉子了"%name) for i in ["alex","wusir","egon","快老師"]: Thread(target=eat1,args=(i,)).start() Thread(target=eat2,args=(i+"2",)).start()信號(hào)量
同進(jìn)程的一樣
Semaphore管理一個(gè)內(nèi)置的計(jì)數(shù)器,
每當(dāng)調(diào)用acquire()時(shí)內(nèi)置計(jì)數(shù)器-1;
調(diào)用release() 時(shí)內(nèi)置計(jì)數(shù)器+1;
計(jì)數(shù)器不能小于0;當(dāng)計(jì)數(shù)器為0時(shí),acquire()將阻塞線程直到其他線程調(diào)用release()。
實(shí)例:(同時(shí)只有5個(gè)線程可以獲得semaphore,即可以限制最大連接數(shù)為5):
from threading import Thread,Semaphore import threading import time def func(): sm.acquire() print("%s get sm" %threading.current_thread().getName()) time.sleep(3) sm.release() if __name__ == "__main__": sm=Semaphore(5) for i in range(23): t=Thread(target=func) t.start()定時(shí)器
from threading import Timer def hello(): print("hello, world") t = Timer(1, hello) t.start() # after 1 seconds, "hello, world" will be printed線程隊(duì)列
queue隊(duì)列 :使用import queue,用法與進(jìn)程Queue一樣
queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.
1.先進(jìn)先出
import queue # q = queue.Queue() 先進(jìn)先出 # 在線程之間數(shù)據(jù)安全,自帶線程鎖的數(shù)據(jù)容器 lq = queue.LifoQueue() # 棧 先進(jìn)后出 算法和數(shù)據(jù)結(jié)構(gòu)中 lq.put(1) lq.put(2) lq.put(3) print(lq.get()) print(lq.get()) print(lq.get()) # print(lq.get())#如果隊(duì)列里邊沒(méi)有值了,進(jìn)行g(shù)et操作,會(huì)堵塞
優(yōu)先級(jí)隊(duì)列
import queue pq = queue.PriorityQueue() # 優(yōu)先級(jí)隊(duì)列 pq.put(3) pq.put(5) pq.put(2) print(pq.get()) print(pq.get()) print(pq.get()) pq.put("c") pq.put("a") pq.put("A") print(pq.get()) print(pq.get()) print(pq.get()) pq.put((10,"asfghfgk")) pq.put((20,"2iyfhejcn")) pq.put((15,"qwuriyhf")) print(pq.get()) print(pq.get()) print(pq.get())Python標(biāo)準(zhǔn)模塊--concurrent.futures
https://docs.python.org/dev/l...
#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,立即返回,并不會(huì)等待池內(nèi)的任務(wù)執(zhí)行完畢 但不管wait參數(shù)為何值,整個(gè)程序都會(huì)等到所有任務(wù)執(zhí)行完畢 submit和map必須在shutdown之前 #result(timeout=None) 取得結(jié)果 #add_done_callback(fn) 回調(diào)函數(shù)
import os import time import random from threading import get_ident from concurrent.futures import ThreadPoolExecutor t_pool = ThreadPoolExecutor(os.cpu_count()) def func(i): time.sleep(random.randint(1,2)) print("線程:{name},任務(wù){(diào)i}".format(name=get_ident(),i=i)) return "*"*i def call_bak(ret): print("線程:{name},返回值長(zhǎng)度:{i}".format(name=get_ident(),i=len(ret.result()))) print("主線程:{name}".format(name=get_ident())) for i in range(1,20): t_pool.submit(func,i).add_done_callback(call_bak)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/42170.html
摘要:進(jìn)程可創(chuàng)建多個(gè)線程來(lái)執(zhí)行同一程序的不同部分。就緒等待線程調(diào)度。運(yùn)行線程正常運(yùn)行阻塞暫停運(yùn)行,解除阻塞后進(jìn)入狀態(tài)重新等待調(diào)度。消亡線程方法執(zhí)行完畢返回或者異常終止。多線程多的情況下,依次執(zhí)行各線程的方法,前頭一個(gè)結(jié)束了才能執(zhí)行后面一個(gè)。 淺談Python多線程 作者簡(jiǎn)介: 姓名:黃志成(小黃)博客: 博客 線程 一.什么是線程? 操作系統(tǒng)原理相關(guān)的書,基本都會(huì)提到一句很經(jīng)典的話: 進(jìn)程...
摘要:上一篇文章進(jìn)程專題完結(jié)篇多進(jìn)程處理的一般建議下一篇文章線程專題多線程使用的必要性進(jìn)程線程進(jìn)程能夠完成多任務(wù),比如在一個(gè)電腦上可以運(yùn)行多個(gè)軟件。由于占用資源少,也使得多線程程序并發(fā)比較高。 上一篇文章:Python進(jìn)程專題完結(jié)篇:多進(jìn)程處理的一般建議下一篇文章:Python線程專題1:多線程使用的必要性 進(jìn)程VS線程 進(jìn)程:能夠完成多任務(wù),比如在一個(gè)電腦上可以運(yùn)行多個(gè)軟件。線程:也能夠...
摘要:其次,解釋器的主循環(huán),一個(gè)名為的函數(shù),讀取字節(jié)碼并逐個(gè)執(zhí)行其中的指令。所有線程都運(yùn)行相同的代碼,并以相同的方式定期從它們獲取鎖定。無(wú)論如何,其他線程無(wú)法并行運(yùn)行。 概述 如今我也是使用Python寫代碼好多年了,但是我卻很少關(guān)心GIL的內(nèi)部機(jī)制,導(dǎo)致在寫Python多線程程序的時(shí)候。今天我們就來(lái)看看CPython的源代碼,探索一下GIL的源碼,了解為什么Python里要存在這個(gè)GIL,...
摘要:也提供多線程支持,而且中的線程并非是模擬出來(lái)的多線程,而是系統(tǒng)級(jí)別的標(biāo)準(zhǔn)庫(kù)提供了兩個(gè)模塊和。同一個(gè)變量,線程則會(huì)互相共享。例如多個(gè)線程對(duì)銀行中的某一個(gè)賬戶進(jìn)行操作。但是實(shí)際情況是隨意切換線程。說(shuō)到的多線程編程,就會(huì)繞不過(guò)。 該文章參考了http://www.liaoxuefeng.com/wi... 廖雪峰的教程。 一個(gè)進(jìn)程至少有一個(gè)線程。Python也提供多線程支持,而且Python...
摘要:如下面的例子,在學(xué)習(xí)線程時(shí),將文件名命名為腳本完全正常沒(méi)問(wèn)題,結(jié)果報(bào)下面的錯(cuò)誤。最大的問(wèn)題就是的多線程程序并不能利用多核的優(yōu)勢(shì)比如一個(gè)使用了多個(gè)線程的計(jì)算密集型程序只會(huì)在一個(gè)單上面運(yùn)行。 本文記錄學(xué)習(xí)Python遇到的問(wèn)題和一些常用用法,注本開發(fā)環(huán)境的Python版本為2.7。 一、python文件命名 在python文件命名時(shí),一定要注意不能和系統(tǒng)默認(rèn)的模塊名沖突,否則會(huì)報(bào)錯(cuò)。如下面...
閱讀 1868·2021-11-25 09:43
閱讀 15738·2021-09-22 15:11
閱讀 2696·2019-08-30 13:19
閱讀 2073·2019-08-30 12:54
閱讀 1887·2019-08-29 13:06
閱讀 1009·2019-08-26 14:07
閱讀 1669·2019-08-26 10:47
閱讀 3118·2019-08-26 10:41